| #![cfg(feature = "invocation")] |
| use std::{convert::TryFrom, str::FromStr}; |
| |
| use jni::{ |
| descriptors::Desc, |
| errors::Error, |
| objects::{ |
| AutoElements, AutoLocal, JByteBuffer, JList, JObject, JString, JThrowable, JValue, |
| ReleaseMode, |
| }, |
| signature::{JavaType, Primitive, ReturnType}, |
| strings::JNIString, |
| sys::{jboolean, jbyte, jchar, jdouble, jfloat, jint, jlong, jobject, jshort, jsize}, |
| JNIEnv, |
| }; |
| |
| mod util; |
| use util::{attach_current_thread, unwrap}; |
| |
| static ARRAYLIST_CLASS: &str = "java/util/ArrayList"; |
| static EXCEPTION_CLASS: &str = "java/lang/Exception"; |
| static ARITHMETIC_EXCEPTION_CLASS: &str = "java/lang/ArithmeticException"; |
| static RUNTIME_EXCEPTION_CLASS: &str = "java/lang/RuntimeException"; |
| static INTEGER_CLASS: &str = "java/lang/Integer"; |
| static MATH_CLASS: &str = "java/lang/Math"; |
| static STRING_CLASS: &str = "java/lang/String"; |
| static MATH_ABS_METHOD_NAME: &str = "abs"; |
| static MATH_TO_INT_METHOD_NAME: &str = "toIntExact"; |
| static MATH_ABS_SIGNATURE: &str = "(I)I"; |
| static MATH_TO_INT_SIGNATURE: &str = "(J)I"; |
| static TEST_EXCEPTION_MESSAGE: &str = "Default exception thrown"; |
| static TESTING_OBJECT_STR: &str = "TESTING OBJECT"; |
| |
| #[test] |
| pub fn call_method_returning_null() { |
| let mut env = attach_current_thread(); |
| // Create an Exception with no message |
| let obj = AutoLocal::new( |
| unwrap(env.new_object(EXCEPTION_CLASS, "()V", &[]), &env), |
| &env, |
| ); |
| // Call Throwable#getMessage must return null |
| let message = unwrap( |
| env.call_method(&obj, "getMessage", "()Ljava/lang/String;", &[]), |
| &env, |
| ); |
| let message_ref = env.auto_local(unwrap(message.l(), &env)); |
| |
| assert!(message_ref.is_null()); |
| } |
| |
| #[test] |
| pub fn is_instance_of_same_class() { |
| let mut env = attach_current_thread(); |
| let obj = AutoLocal::new( |
| unwrap(env.new_object(EXCEPTION_CLASS, "()V", &[]), &env), |
| &env, |
| ); |
| assert!(unwrap(env.is_instance_of(&obj, EXCEPTION_CLASS), &env)); |
| } |
| |
| #[test] |
| pub fn is_instance_of_superclass() { |
| let mut env = attach_current_thread(); |
| let obj = AutoLocal::new( |
| unwrap(env.new_object(ARITHMETIC_EXCEPTION_CLASS, "()V", &[]), &env), |
| &env, |
| ); |
| assert!(unwrap(env.is_instance_of(&obj, EXCEPTION_CLASS), &env)); |
| } |
| |
| #[test] |
| pub fn is_instance_of_subclass() { |
| let mut env = attach_current_thread(); |
| let obj = AutoLocal::new( |
| unwrap(env.new_object(EXCEPTION_CLASS, "()V", &[]), &env), |
| &env, |
| ); |
| assert!(!unwrap( |
| env.is_instance_of(&obj, ARITHMETIC_EXCEPTION_CLASS), |
| &env, |
| )); |
| } |
| |
| #[test] |
| pub fn is_instance_of_not_superclass() { |
| let mut env = attach_current_thread(); |
| let obj = AutoLocal::new( |
| unwrap(env.new_object(ARITHMETIC_EXCEPTION_CLASS, "()V", &[]), &env), |
| &env, |
| ); |
| assert!(!unwrap(env.is_instance_of(&obj, ARRAYLIST_CLASS), &env)); |
| } |
| |
| #[test] |
| pub fn is_instance_of_null() { |
| let mut env = attach_current_thread(); |
| let obj = JObject::null(); |
| assert!(unwrap(env.is_instance_of(&obj, ARRAYLIST_CLASS), &env)); |
| assert!(unwrap(env.is_instance_of(&obj, EXCEPTION_CLASS), &env)); |
| assert!(unwrap( |
| env.is_instance_of(&obj, ARITHMETIC_EXCEPTION_CLASS), |
| &env, |
| )); |
| } |
| |
| #[test] |
| pub fn is_same_object_diff_references() { |
| let env = attach_current_thread(); |
| let string = env.new_string(TESTING_OBJECT_STR).unwrap(); |
| let ref_from_string = unwrap(env.new_local_ref(&string), &env); |
| assert!(unwrap(env.is_same_object(&string, &ref_from_string), &env)); |
| unwrap(env.delete_local_ref(ref_from_string), &env); |
| } |
| |
| #[test] |
| pub fn is_same_object_same_reference() { |
| let env = attach_current_thread(); |
| let string = env.new_string(TESTING_OBJECT_STR).unwrap(); |
| assert!(unwrap(env.is_same_object(&string, &string), &env)); |
| } |
| |
| #[test] |
| pub fn is_not_same_object() { |
| let env = attach_current_thread(); |
| let string = env.new_string(TESTING_OBJECT_STR).unwrap(); |
| let same_src_str = env.new_string(TESTING_OBJECT_STR).unwrap(); |
| assert!(!unwrap(env.is_same_object(string, same_src_str), &env)); |
| } |
| |
| #[test] |
| pub fn is_not_same_object_null() { |
| let env = attach_current_thread(); |
| assert!(unwrap( |
| env.is_same_object(JObject::null(), JObject::null()), |
| &env, |
| )); |
| } |
| |
| #[test] |
| pub fn get_static_public_field() { |
| let mut env = attach_current_thread(); |
| |
| let min_int_value = env |
| .get_static_field(INTEGER_CLASS, "MIN_VALUE", "I") |
| .unwrap() |
| .i() |
| .unwrap(); |
| |
| assert_eq!(min_int_value, i32::min_value()); |
| } |
| |
| #[test] |
| pub fn get_static_public_field_by_id() { |
| let mut env = attach_current_thread(); |
| |
| // One can't pass a JavaType::Primitive(Primitive::Int) to |
| // `get_static_field_id` unfortunately: #137 |
| let field_type = "I"; |
| let field_id = env |
| .get_static_field_id(INTEGER_CLASS, "MIN_VALUE", field_type) |
| .unwrap(); |
| |
| let field_type = JavaType::from_str(field_type).unwrap(); |
| let min_int_value = env |
| .get_static_field_unchecked(INTEGER_CLASS, field_id, field_type) |
| .unwrap() |
| .i() |
| .unwrap(); |
| |
| assert_eq!(min_int_value, i32::min_value()); |
| } |
| |
| #[test] |
| pub fn pop_local_frame_pending_exception() { |
| let mut env = attach_current_thread(); |
| |
| env.push_local_frame(16).unwrap(); |
| |
| env.throw_new(RUNTIME_EXCEPTION_CLASS, "Test Exception") |
| .unwrap(); |
| |
| // Pop the local frame with a pending exception |
| unsafe { env.pop_local_frame(&JObject::null()) } |
| .expect("JNIEnv#pop_local_frame must work in case of pending exception"); |
| |
| env.exception_clear().unwrap(); |
| } |
| |
| #[test] |
| pub fn push_local_frame_pending_exception() { |
| let mut env = attach_current_thread(); |
| |
| env.throw_new(RUNTIME_EXCEPTION_CLASS, "Test Exception") |
| .unwrap(); |
| |
| // Push a new local frame with a pending exception |
| env.push_local_frame(16) |
| .expect("JNIEnv#push_local_frame must work in case of pending exception"); |
| |
| env.exception_clear().unwrap(); |
| |
| unsafe { env.pop_local_frame(&JObject::null()) }.unwrap(); |
| } |
| |
| #[test] |
| pub fn push_local_frame_too_many_refs() { |
| let env = attach_current_thread(); |
| |
| // Try to push a new local frame with a ridiculous size |
| let frame_size = i32::max_value(); |
| env.push_local_frame(frame_size) |
| .expect_err("push_local_frame(2B) must Err"); |
| |
| unsafe { env.pop_local_frame(&JObject::null()) }.unwrap(); |
| } |
| |
| #[test] |
| pub fn with_local_frame() { |
| let mut env = attach_current_thread(); |
| |
| let s = env |
| .with_local_frame_returning_local::<_, jni::errors::Error>(16, |env| { |
| let res = env.new_string("Test")?; |
| Ok(res.into()) |
| }) |
| .unwrap() |
| .into(); |
| |
| let s = env |
| .get_string(&s) |
| .expect("The object returned from the local frame must remain valid"); |
| assert_eq!(s.to_str().unwrap(), "Test"); |
| } |
| |
| #[test] |
| pub fn with_local_frame_pending_exception() { |
| let mut env = attach_current_thread(); |
| |
| env.throw_new(RUNTIME_EXCEPTION_CLASS, "Test Exception") |
| .unwrap(); |
| |
| // Try to allocate a frame of locals |
| env.with_local_frame(16, |_| -> Result<_, Error> { Ok(()) }) |
| .expect("JNIEnv#with_local_frame must work in case of pending exception"); |
| |
| env.exception_clear().unwrap(); |
| } |
| |
| #[test] |
| pub fn call_method_ok() { |
| let mut env = attach_current_thread(); |
| |
| let s = env.new_string(TESTING_OBJECT_STR).unwrap(); |
| |
| let v: jint = env |
| .call_method(s, "indexOf", "(I)I", &[JValue::Int('S' as i32)]) |
| .expect("JNIEnv#call_method should return JValue") |
| .i() |
| .unwrap(); |
| |
| assert_eq!(v, 2); |
| } |
| |
| #[test] |
| pub fn call_method_with_bad_args_errs() { |
| let mut env = attach_current_thread(); |
| |
| let s = env.new_string(TESTING_OBJECT_STR).unwrap(); |
| |
| let is_bad_typ = env |
| .call_method( |
| &s, |
| "indexOf", |
| "(I)I", |
| &[JValue::Float(std::f32::consts::PI)], |
| ) |
| .map_err(|error| matches!(error, Error::InvalidArgList(_))) |
| .expect_err("JNIEnv#callmethod with bad arg type should err"); |
| |
| assert!( |
| is_bad_typ, |
| "ErrorKind::InvalidArgList expected when passing bad value type" |
| ); |
| |
| let is_bad_len = env |
| .call_method( |
| &s, |
| "indexOf", |
| "(I)I", |
| &[JValue::Int('S' as i32), JValue::Long(3)], |
| ) |
| .map_err(|error| matches!(error, Error::InvalidArgList(_))) |
| .expect_err("JNIEnv#call_method with bad arg lengths should err"); |
| |
| assert!( |
| is_bad_len, |
| "ErrorKind::InvalidArgList expected when passing bad argument lengths" |
| ); |
| } |
| |
| #[test] |
| pub fn call_static_method_ok() { |
| let mut env = attach_current_thread(); |
| |
| let x = JValue::from(-10); |
| let val: jint = env |
| .call_static_method(MATH_CLASS, MATH_ABS_METHOD_NAME, MATH_ABS_SIGNATURE, &[x]) |
| .expect("JNIEnv#call_static_method should return JValue") |
| .i() |
| .unwrap(); |
| |
| assert_eq!(val, 10); |
| } |
| |
| #[test] |
| pub fn call_static_method_unchecked_ok() { |
| let mut env = attach_current_thread(); |
| |
| let x = JValue::from(-10); |
| let math_class = env.find_class(MATH_CLASS).unwrap(); |
| let abs_method_id = env |
| .get_static_method_id(&math_class, MATH_ABS_METHOD_NAME, MATH_ABS_SIGNATURE) |
| .unwrap(); |
| let val: jint = unsafe { |
| env.call_static_method_unchecked( |
| &math_class, |
| abs_method_id, |
| ReturnType::Primitive(Primitive::Int), |
| &[x.as_jni()], |
| ) |
| } |
| .expect("JNIEnv#call_static_method_unchecked should return JValue") |
| .i() |
| .unwrap(); |
| |
| assert_eq!(val, 10); |
| } |
| |
| #[test] |
| pub fn call_new_object_unchecked_ok() { |
| let mut env = attach_current_thread(); |
| |
| let test_str = env.new_string(TESTING_OBJECT_STR).unwrap(); |
| let string_class = env.find_class(STRING_CLASS).unwrap(); |
| |
| let ctor_method_id = env |
| .get_method_id(&string_class, "<init>", "(Ljava/lang/String;)V") |
| .unwrap(); |
| let val: JObject = unsafe { |
| env.new_object_unchecked( |
| &string_class, |
| ctor_method_id, |
| &[JValue::from(&test_str).as_jni()], |
| ) |
| } |
| .expect("JNIEnv#new_object_unchecked should return JValue"); |
| |
| let jstr = JString::try_from(val).expect("asd"); |
| let javastr = env.get_string(&jstr).unwrap(); |
| let rstr = javastr.to_str().unwrap(); |
| assert_eq!(rstr, TESTING_OBJECT_STR); |
| } |
| |
| #[test] |
| pub fn call_new_object_with_bad_args_errs() { |
| let mut env = attach_current_thread(); |
| |
| let string_class = env.find_class(STRING_CLASS).unwrap(); |
| |
| let is_bad_typ = env |
| .new_object(&string_class, "(Ljava/lang/String;)V", &[JValue::Int(2)]) |
| .map_err(|error| matches!(error, Error::InvalidArgList(_))) |
| .expect_err("JNIEnv#new_object with bad arg type should err"); |
| |
| assert!( |
| is_bad_typ, |
| "ErrorKind::InvalidArgList expected when passing bad value type" |
| ); |
| |
| let s = env.new_string(TESTING_OBJECT_STR).unwrap(); |
| |
| let is_bad_len = env |
| .new_object( |
| &string_class, |
| "(Ljava/lang/String;)V", |
| &[JValue::from(&s), JValue::Int(2)], |
| ) |
| .map_err(|error| matches!(error, Error::InvalidArgList(_))) |
| .expect_err("JNIEnv#new_object with bad arg type should err"); |
| |
| assert!( |
| is_bad_len, |
| "ErrorKind::InvalidArgList expected when passing bad argument lengths" |
| ); |
| } |
| |
| /// Check that we get a runtime error if trying to instantiate with an array class. |
| /// |
| /// Although the JNI spec for `NewObjectA` states that the class "must not refer to an array class" |
| /// (and could therefor potentially trigger undefined behaviour if that rule is violated) we |
| /// expect that `JNIEnv::new_object()` shouldn't ever get as far as calling `NewObjectA` since |
| /// it will first fail (with a safe, runtime error) to lookup a method ID for any constructor. |
| /// (consistent with how [getConstructors()](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Class.html#getConstructors()) |
| /// doesn't expose constructors for array classes) |
| #[test] |
| pub fn call_new_object_with_array_class() { |
| let mut env = attach_current_thread(); |
| |
| let byte_array = env.new_byte_array(16).unwrap(); |
| let array_class = env.get_object_class(byte_array).unwrap(); |
| // We just make up a plausible constructor signature |
| let result = env.new_object(&array_class, "(I)[B", &[JValue::Int(16)]); |
| |
| assert!(result.is_err()) |
| } |
| |
| #[test] |
| pub fn call_static_method_throws() { |
| let mut env = attach_current_thread(); |
| |
| let x = JValue::Long(4_000_000_000); |
| let is_java_exception = env |
| .call_static_method( |
| MATH_CLASS, |
| MATH_TO_INT_METHOD_NAME, |
| MATH_TO_INT_SIGNATURE, |
| &[x], |
| ) |
| .map_err(|error| matches!(error, Error::JavaException)) |
| .expect_err("JNIEnv#call_static_method_unsafe should return error"); |
| |
| // Throws a java.lang.ArithmeticException: integer overflow |
| assert!( |
| is_java_exception, |
| "ErrorKind::JavaException expected as error" |
| ); |
| assert_pending_java_exception(&mut env); |
| } |
| |
| #[test] |
| pub fn call_static_method_with_bad_args_errs() { |
| let mut env = attach_current_thread(); |
| |
| let x = JValue::Double(4.567_891_23); |
| let is_bad_typ = env |
| .call_static_method( |
| MATH_CLASS, |
| MATH_TO_INT_METHOD_NAME, |
| MATH_TO_INT_SIGNATURE, |
| &[x], |
| ) |
| .map_err(|error| matches!(error, Error::InvalidArgList(_))) |
| .expect_err("JNIEnv#call_static_method with bad arg type should err"); |
| |
| assert!( |
| is_bad_typ, |
| "ErrorKind::InvalidArgList expected when passing bad value type" |
| ); |
| |
| let is_bad_len = env |
| .call_static_method( |
| MATH_CLASS, |
| MATH_TO_INT_METHOD_NAME, |
| MATH_TO_INT_SIGNATURE, |
| &[JValue::Int(2), JValue::Int(3)], |
| ) |
| .map_err(|error| matches!(error, Error::InvalidArgList(_))) |
| .expect_err("JNIEnv#call_static_method with bad arg lengths should err"); |
| |
| assert!( |
| is_bad_len, |
| "ErrorKind::InvalidArgList expected when passing bad argument lengths" |
| ); |
| } |
| |
| #[test] |
| pub fn java_byte_array_from_slice() { |
| let env = attach_current_thread(); |
| let buf: &[u8] = &[1, 2, 3]; |
| let java_array = AutoLocal::new( |
| env.byte_array_from_slice(buf) |
| .expect("JNIEnv#byte_array_from_slice must create a java array from slice"), |
| &env, |
| ); |
| |
| assert!(!java_array.is_null()); |
| let mut res: [i8; 3] = [0; 3]; |
| env.get_byte_array_region(&java_array, 0, &mut res).unwrap(); |
| assert_eq!(res[0], 1); |
| assert_eq!(res[1], 2); |
| assert_eq!(res[2], 3); |
| } |
| |
| macro_rules! test_auto_array_read_write { |
| ( $test_name:tt, $jni_type:ty, $new_array:tt, $get_array:tt, $set_array:tt ) => { |
| #[test] |
| pub fn $test_name() { |
| let env = attach_current_thread(); |
| |
| // Create original Java array |
| let buf: &[$jni_type] = &[0 as $jni_type, 1 as $jni_type]; |
| let java_array = env |
| .$new_array(2) |
| .expect(stringify!(JNIEnv#$new_array must create a Java $jni_type array with given size)); |
| |
| // Insert array elements |
| let _ = env.$set_array(&java_array, 0, buf); |
| |
| // Use a scope to test Drop |
| { |
| // Get byte array elements auto wrapper |
| let mut auto_ptr: AutoElements<$jni_type> = unsafe { |
| // Make sure the lifetime is tied to the environment, |
| // not the particular JNIEnv reference |
| let mut temporary_env: JNIEnv = env.unsafe_clone(); |
| temporary_env.get_array_elements(&java_array, ReleaseMode::CopyBack).unwrap() |
| }; |
| |
| // Check array size |
| assert_eq!(auto_ptr.len(), 2); |
| |
| // Check pointer access |
| let ptr = auto_ptr.as_ptr(); |
| assert_eq!(unsafe { *ptr.offset(0) } as i32, 0); |
| assert_eq!(unsafe { *ptr.offset(1) } as i32, 1); |
| |
| // Check pointer From access |
| let ptr: *mut $jni_type = std::convert::From::from(&auto_ptr); |
| assert_eq!(unsafe { *ptr.offset(0) } as i32, 0); |
| assert_eq!(unsafe { *ptr.offset(1) } as i32, 1); |
| |
| // Check pointer into() access |
| let ptr: *mut $jni_type = (&auto_ptr).into(); |
| assert_eq!(unsafe { *ptr.offset(0) } as i32, 0); |
| assert_eq!(unsafe { *ptr.offset(1) } as i32, 1); |
| |
| // Check slice access |
| // |
| // # Safety |
| // |
| // We make sure that the slice is dropped before also testing access via `Deref` |
| // (to ensure we don't have aliased references) |
| unsafe { |
| let slice = std::slice::from_raw_parts(auto_ptr.as_ptr(), auto_ptr.len()); |
| assert_eq!(slice[0] as i32, 0); |
| assert_eq!(slice[1] as i32, 1); |
| } |
| |
| // Check access via Deref |
| assert_eq!(auto_ptr[0] as i32, 0); |
| assert_eq!(auto_ptr[1] as i32, 1); |
| |
| // Modify via DerefMut |
| let tmp = auto_ptr[1]; |
| auto_ptr[1] = auto_ptr[0]; |
| auto_ptr[0] = tmp; |
| |
| // Commit would be necessary here, if there were no closure |
| //auto_ptr.commit().unwrap(); |
| } |
| |
| // Confirm modification of original Java array |
| let mut res: [$jni_type; 2] = [0 as $jni_type; 2]; |
| env.$get_array(&java_array, 0, &mut res).unwrap(); |
| assert_eq!(res[0] as i32, 1); |
| assert_eq!(res[1] as i32, 0); |
| } |
| }; |
| } |
| |
| // Test generic get_array_elements |
| test_auto_array_read_write!( |
| get_array_elements, |
| jint, |
| new_int_array, |
| get_int_array_region, |
| set_int_array_region |
| ); |
| |
| // Test type-specific array accessors |
| test_auto_array_read_write!( |
| get_int_array_elements, |
| jint, |
| new_int_array, |
| get_int_array_region, |
| set_int_array_region |
| ); |
| |
| test_auto_array_read_write!( |
| get_long_array_elements, |
| jlong, |
| new_long_array, |
| get_long_array_region, |
| set_long_array_region |
| ); |
| |
| test_auto_array_read_write!( |
| get_byte_array_elements, |
| jbyte, |
| new_byte_array, |
| get_byte_array_region, |
| set_byte_array_region |
| ); |
| |
| test_auto_array_read_write!( |
| get_boolean_array_elements, |
| jboolean, |
| new_boolean_array, |
| get_boolean_array_region, |
| set_boolean_array_region |
| ); |
| |
| test_auto_array_read_write!( |
| get_char_array_elements, |
| jchar, |
| new_char_array, |
| get_char_array_region, |
| set_char_array_region |
| ); |
| |
| test_auto_array_read_write!( |
| get_short_array_elements, |
| jshort, |
| new_short_array, |
| get_short_array_region, |
| set_short_array_region |
| ); |
| |
| test_auto_array_read_write!( |
| get_float_array_elements, |
| jfloat, |
| new_float_array, |
| get_float_array_region, |
| set_float_array_region |
| ); |
| |
| test_auto_array_read_write!( |
| get_double_array_elements, |
| jdouble, |
| new_double_array, |
| get_double_array_region, |
| set_double_array_region |
| ); |
| |
| #[test] |
| #[ignore] // Disabled until issue #283 is resolved |
| pub fn get_long_array_elements_commit() { |
| let mut env = attach_current_thread(); |
| |
| // Create original Java array |
| let buf: &[i64] = &[1, 2, 3]; |
| let java_array = env |
| .new_long_array(3) |
| .expect("JNIEnv#new_long_array must create a java array with given size"); |
| |
| // Insert array elements |
| let _ = env.set_long_array_region(&java_array, 0, buf); |
| |
| // Get long array elements auto wrapper |
| let mut auto_ptr = unsafe { |
| env.get_array_elements(&java_array, ReleaseMode::CopyBack) |
| .unwrap() |
| }; |
| |
| // Copying the array depends on the VM vendor/version/GC combinations. |
| // If the wrapped array is not being copied, we can skip the test. |
| if !auto_ptr.is_copy() { |
| return; |
| } |
| |
| // Check pointer access |
| let ptr = auto_ptr.as_ptr(); |
| |
| // Modify |
| unsafe { |
| *ptr.offset(0) += 1; |
| *ptr.offset(1) += 1; |
| *ptr.offset(2) += 1; |
| } |
| |
| // Check that original Java array is unmodified |
| let mut res: [i64; 3] = [0; 3]; |
| env.get_long_array_region(&java_array, 0, &mut res).unwrap(); |
| assert_eq!(res[0], 1); |
| assert_eq!(res[1], 2); |
| assert_eq!(res[2], 3); |
| |
| auto_ptr.commit().unwrap(); |
| |
| // Confirm modification of original Java array |
| env.get_long_array_region(&java_array, 0, &mut res).unwrap(); |
| assert_eq!(res[0], 2); |
| assert_eq!(res[1], 3); |
| assert_eq!(res[2], 4); |
| } |
| |
| #[test] |
| pub fn get_array_elements_critical() { |
| let mut env = attach_current_thread(); |
| |
| // Create original Java array |
| let buf: &[u8] = &[1, 2, 3]; |
| let java_array = env |
| .byte_array_from_slice(buf) |
| .expect("JNIEnv#byte_array_from_slice must create a java array from slice"); |
| |
| // Use a scope to test Drop |
| { |
| // Get primitive array elements auto wrapper |
| let mut auto_ptr = unsafe { |
| env.get_array_elements_critical(&java_array, ReleaseMode::CopyBack) |
| .unwrap() |
| }; |
| |
| // Check array size |
| assert_eq!(auto_ptr.len(), 3); |
| |
| // Convert void pointer to a &[i8] slice, without copy |
| // |
| // # Safety |
| // |
| // We make sure that the slice is dropped before also testing access via `Deref` |
| // (to ensure we don't have aliased references) |
| unsafe { |
| let slice = std::slice::from_raw_parts(auto_ptr.as_ptr(), auto_ptr.len()); |
| assert_eq!(slice[0], 1); |
| assert_eq!(slice[1], 2); |
| assert_eq!(slice[2], 3); |
| } |
| |
| // Also check access via `Deref` |
| assert_eq!(auto_ptr[0], 1); |
| assert_eq!(auto_ptr[1], 2); |
| assert_eq!(auto_ptr[2], 3); |
| |
| // Modify via `DerefMut` |
| auto_ptr[0] += 1; |
| auto_ptr[1] += 1; |
| auto_ptr[2] += 1; |
| } |
| |
| // Confirm modification of original Java array |
| let mut res: [i8; 3] = [0; 3]; |
| env.get_byte_array_region(&java_array, 0, &mut res).unwrap(); |
| assert_eq!(res[0], 2); |
| assert_eq!(res[1], 3); |
| assert_eq!(res[2], 4); |
| } |
| |
| #[test] |
| pub fn get_object_class() { |
| let env = attach_current_thread(); |
| let string = env.new_string("test").unwrap(); |
| let result = env.get_object_class(string); |
| assert!(result.is_ok()); |
| assert!(!result.unwrap().is_null()); |
| } |
| |
| #[test] |
| pub fn get_object_class_null_arg() { |
| let env = attach_current_thread(); |
| let null_obj = JObject::null(); |
| let result = env |
| .get_object_class(null_obj) |
| .map_err(|error| matches!(error, Error::NullPtr(_))) |
| .expect_err("JNIEnv#get_object_class should return error for null argument"); |
| assert!(result, "ErrorKind::NullPtr expected as error"); |
| } |
| |
| #[test] |
| pub fn new_direct_byte_buffer() { |
| let mut env = attach_current_thread(); |
| let vec: Vec<u8> = vec![0, 1, 2, 3]; |
| let (addr, len) = { |
| // (would use buf.into_raw_parts() on nightly) |
| let buf = vec.leak(); |
| (buf.as_mut_ptr(), buf.len()) |
| }; |
| let result = unsafe { env.new_direct_byte_buffer(addr, len) }; |
| assert!(result.is_ok()); |
| assert!(!result.unwrap().is_null()); |
| } |
| |
| #[test] |
| pub fn new_direct_byte_buffer_invalid_addr() { |
| let mut env = attach_current_thread(); |
| let result = unsafe { env.new_direct_byte_buffer(std::ptr::null_mut(), 5) }; |
| assert!(result.is_err()); |
| } |
| |
| #[test] |
| pub fn get_direct_buffer_capacity_ok() { |
| let mut env = attach_current_thread(); |
| let vec: Vec<u8> = vec![0, 1, 2, 3]; |
| let (addr, len) = { |
| // (would use buf.into_raw_parts() on nightly) |
| let buf = vec.leak(); |
| (buf.as_mut_ptr(), buf.len()) |
| }; |
| let result = unsafe { env.new_direct_byte_buffer(addr, len) }.unwrap(); |
| assert!(!result.is_null()); |
| |
| let capacity = env.get_direct_buffer_capacity(&result).unwrap(); |
| assert_eq!(capacity, 4); |
| } |
| |
| #[test] |
| pub fn get_direct_buffer_capacity_wrong_arg() { |
| let env = attach_current_thread(); |
| let wrong_obj = unsafe { JByteBuffer::from_raw(env.new_string("wrong").unwrap().into_raw()) }; |
| let capacity = env.get_direct_buffer_capacity(&wrong_obj); |
| assert!(capacity.is_err()); |
| } |
| |
| #[test] |
| pub fn get_direct_buffer_capacity_null_arg() { |
| let env = attach_current_thread(); |
| let result = env.get_direct_buffer_capacity(&JObject::null().into()); |
| assert!(result.is_err()); |
| } |
| |
| #[test] |
| pub fn get_direct_buffer_address_ok() { |
| let mut env = attach_current_thread(); |
| let vec: Vec<u8> = vec![0, 1, 2, 3]; |
| let (addr, len) = { |
| // (would use buf.into_raw_parts() on nightly) |
| let buf = vec.leak(); |
| (buf.as_mut_ptr(), buf.len()) |
| }; |
| let result = unsafe { env.new_direct_byte_buffer(addr, len) }.unwrap(); |
| assert!(!result.is_null()); |
| |
| let dest_buffer = env.get_direct_buffer_address(&result).unwrap(); |
| assert_eq!(addr, dest_buffer); |
| } |
| |
| #[test] |
| pub fn get_direct_buffer_address_wrong_arg() { |
| let env = attach_current_thread(); |
| let wrong_obj: JObject = env.new_string("wrong").unwrap().into(); |
| let result = env.get_direct_buffer_address(&wrong_obj.into()); |
| assert!(result.is_err()); |
| } |
| |
| #[test] |
| pub fn get_direct_buffer_address_null_arg() { |
| let env = attach_current_thread(); |
| let result = env.get_direct_buffer_address(&JObject::null().into()); |
| assert!(result.is_err()); |
| } |
| |
| // Group test for testing the family of new_PRIMITIVE_array functions with correct arguments |
| #[test] |
| pub fn new_primitive_array_ok() { |
| let env = attach_current_thread(); |
| const SIZE: jsize = 16; |
| |
| let result = env.new_boolean_array(SIZE); |
| assert!(result.is_ok()); |
| assert!(!result.unwrap().is_null()); |
| |
| let result = env.new_byte_array(SIZE); |
| assert!(result.is_ok()); |
| assert!(!result.unwrap().is_null()); |
| |
| let result = env.new_char_array(SIZE); |
| assert!(result.is_ok()); |
| assert!(!result.unwrap().is_null()); |
| |
| let result = env.new_short_array(SIZE); |
| assert!(result.is_ok()); |
| assert!(!result.unwrap().is_null()); |
| |
| let result = env.new_int_array(SIZE); |
| assert!(result.is_ok()); |
| assert!(!result.unwrap().is_null()); |
| |
| let result = env.new_long_array(SIZE); |
| assert!(result.is_ok()); |
| assert!(!result.unwrap().is_null()); |
| |
| let result = env.new_float_array(SIZE); |
| assert!(result.is_ok()); |
| assert!(!result.unwrap().is_null()); |
| |
| let result = env.new_double_array(SIZE); |
| assert!(result.is_ok()); |
| assert!(!result.unwrap().is_null()); |
| } |
| |
| // Group test for testing the family of new_PRIMITIVE_array functions with wrong arguments |
| #[test] |
| pub fn new_primitive_array_wrong() { |
| let mut env = attach_current_thread(); |
| const WRONG_SIZE: jsize = -1; |
| |
| let result = env.new_boolean_array(WRONG_SIZE).map(|arr| arr.as_raw()); |
| assert_exception(&result, "JNIEnv#new_boolean_array should throw exception"); |
| assert_pending_java_exception(&mut env); |
| |
| let result = env.new_byte_array(WRONG_SIZE).map(|arr| arr.as_raw()); |
| assert_exception(&result, "JNIEnv#new_byte_array should throw exception"); |
| assert_pending_java_exception(&mut env); |
| |
| let result = env.new_char_array(WRONG_SIZE).map(|arr| arr.as_raw()); |
| assert_exception(&result, "JNIEnv#new_char_array should throw exception"); |
| assert_pending_java_exception(&mut env); |
| |
| let result = env.new_short_array(WRONG_SIZE).map(|arr| arr.as_raw()); |
| assert_exception(&result, "JNIEnv#new_short_array should throw exception"); |
| assert_pending_java_exception(&mut env); |
| |
| let result = env.new_int_array(WRONG_SIZE).map(|arr| arr.as_raw()); |
| assert_exception(&result, "JNIEnv#new_int_array should throw exception"); |
| assert_pending_java_exception(&mut env); |
| |
| let result = env.new_long_array(WRONG_SIZE).map(|arr| arr.as_raw()); |
| assert_exception(&result, "JNIEnv#new_long_array should throw exception"); |
| assert_pending_java_exception(&mut env); |
| |
| let result = env.new_float_array(WRONG_SIZE).map(|arr| arr.as_raw()); |
| assert_exception(&result, "JNIEnv#new_float_array should throw exception"); |
| assert_pending_java_exception(&mut env); |
| |
| let result = env.new_double_array(WRONG_SIZE).map(|arr| arr.as_raw()); |
| assert_exception(&result, "JNIEnv#new_double_array should throw exception"); |
| assert_pending_java_exception(&mut env); |
| } |
| |
| #[test] |
| fn get_super_class_ok() { |
| let mut env = attach_current_thread(); |
| let result = env.get_superclass(ARRAYLIST_CLASS); |
| assert!(result.is_ok()); |
| assert!(result.unwrap().is_some()); |
| } |
| |
| #[test] |
| fn get_super_class_null() { |
| let mut env = attach_current_thread(); |
| let result = env.get_superclass("java/lang/Object"); |
| assert!(result.is_ok()); |
| assert!(result.unwrap().is_none()); |
| } |
| |
| #[test] |
| fn convert_byte_array() { |
| let env = attach_current_thread(); |
| let src: Vec<u8> = vec![1, 2, 3, 4]; |
| let java_byte_array = env.byte_array_from_slice(&src).unwrap(); |
| |
| let dest = env.convert_byte_array(java_byte_array); |
| assert!(dest.is_ok()); |
| assert_eq!(dest.unwrap(), src); |
| } |
| |
| #[test] |
| fn local_ref_null() { |
| let env = attach_current_thread(); |
| let null_obj = JObject::null(); |
| |
| let result = env.new_local_ref::<&JObject>(&null_obj); |
| assert!(result.is_ok()); |
| assert!(result.unwrap().is_null()); |
| |
| // try to delete null reference |
| let result = env.delete_local_ref(null_obj); |
| assert!(result.is_ok()); |
| } |
| |
| #[test] |
| fn new_global_ref_null() { |
| let env = attach_current_thread(); |
| let null_obj = JObject::null(); |
| let result = env.new_global_ref(null_obj); |
| assert!(result.is_ok()); |
| assert!(result.unwrap().is_null()); |
| } |
| |
| #[test] |
| fn new_weak_ref_null() { |
| let env = attach_current_thread(); |
| let null_obj = JObject::null(); |
| let result = unwrap(env.new_weak_ref(null_obj), &env); |
| assert!(result.is_none()); |
| } |
| |
| #[test] |
| fn auto_local_null() { |
| let env = attach_current_thread(); |
| let null_obj = JObject::null(); |
| { |
| let auto_ref = AutoLocal::new(null_obj, &env); |
| assert!(auto_ref.is_null()); |
| } |
| } |
| |
| #[test] |
| fn short_lifetime_with_local_frame() { |
| let mut env = attach_current_thread(); |
| let object = short_lifetime_with_local_frame_sub_fn(&mut env); |
| assert!(object.is_ok()); |
| } |
| |
| fn short_lifetime_with_local_frame_sub_fn<'local>( |
| env: &'_ mut JNIEnv<'local>, |
| ) -> Result<JObject<'local>, Error> { |
| env.with_local_frame_returning_local(16, |env| { |
| env.new_object(INTEGER_CLASS, "(I)V", &[JValue::from(5)]) |
| }) |
| } |
| |
| #[test] |
| fn short_lifetime_list() { |
| let mut env = attach_current_thread(); |
| let first_list_object = short_lifetime_list_sub_fn(&mut env).unwrap(); |
| let value = env.call_method(first_list_object, "intValue", "()I", &[]); |
| assert_eq!(value.unwrap().i().unwrap(), 1); |
| } |
| |
| fn short_lifetime_list_sub_fn<'local>( |
| env: &'_ mut JNIEnv<'local>, |
| ) -> Result<JObject<'local>, Error> { |
| let list_object = env.new_object(ARRAYLIST_CLASS, "()V", &[])?; |
| let list = JList::from_env(env, &list_object)?; |
| let element = env.new_object(INTEGER_CLASS, "(I)V", &[JValue::from(1)])?; |
| list.add(env, &element)?; |
| short_lifetime_list_sub_fn_get_first_element(env, &list) |
| } |
| |
| fn short_lifetime_list_sub_fn_get_first_element<'local>( |
| env: &'_ mut JNIEnv<'local>, |
| list: &'_ JList<'local, '_, '_>, |
| ) -> Result<JObject<'local>, Error> { |
| let mut iterator = list.iter(env)?; |
| Ok(iterator.next(env)?.unwrap()) |
| } |
| |
| #[test] |
| fn get_object_array_element() { |
| let mut env = attach_current_thread(); |
| let array = env |
| .new_object_array(1, STRING_CLASS, JObject::null()) |
| .unwrap(); |
| assert!(!array.is_null()); |
| assert!(env.get_object_array_element(&array, 0).unwrap().is_null()); |
| let test_str = env.new_string("test").unwrap(); |
| env.set_object_array_element(&array, 0, test_str).unwrap(); |
| assert!(!env.get_object_array_element(&array, 0).unwrap().is_null()); |
| } |
| |
| #[test] |
| pub fn throw_new() { |
| let mut env = attach_current_thread(); |
| |
| let result = env.throw_new(RUNTIME_EXCEPTION_CLASS, "Test Exception"); |
| assert!(result.is_ok()); |
| assert_pending_java_exception_detailed( |
| &mut env, |
| Some(RUNTIME_EXCEPTION_CLASS), |
| Some("Test Exception"), |
| ); |
| } |
| |
| #[test] |
| pub fn throw_new_fail() { |
| let mut env = attach_current_thread(); |
| |
| let result = env.throw_new("java/lang/NonexistentException", "Test Exception"); |
| assert!(result.is_err()); |
| // Just to clear the java.lang.NoClassDefFoundError |
| assert_pending_java_exception(&mut env); |
| } |
| |
| #[test] |
| pub fn throw_defaults() { |
| let mut env = attach_current_thread(); |
| |
| test_throwable_descriptor_with_default_type(&mut env, TEST_EXCEPTION_MESSAGE); |
| test_throwable_descriptor_with_default_type(&mut env, TEST_EXCEPTION_MESSAGE.to_owned()); |
| test_throwable_descriptor_with_default_type(&mut env, JNIString::from(TEST_EXCEPTION_MESSAGE)); |
| } |
| |
| #[test] |
| pub fn test_conversion() { |
| let env = attach_current_thread(); |
| let orig_obj: JObject = env.new_string("Hello, world!").unwrap().into(); |
| |
| let obj: JObject = unwrap(env.new_local_ref(&orig_obj), &env); |
| let string = JString::from(obj); |
| let actual = JObject::from(string); |
| assert!(unwrap(env.is_same_object(&orig_obj, actual), &env)); |
| |
| let global_ref = env.new_global_ref(&orig_obj).unwrap(); |
| assert!(unwrap(env.is_same_object(&orig_obj, global_ref), &env)); |
| |
| let weak_ref = unwrap(env.new_weak_ref(&orig_obj), &env).expect("weak ref should not be null"); |
| let actual = |
| unwrap(weak_ref.upgrade_local(&env), &env).expect("weak ref should not have been GC'd"); |
| assert!(unwrap(env.is_same_object(&orig_obj, actual), &env)); |
| |
| let obj: JObject = unwrap(env.new_local_ref(&orig_obj), &env); |
| let auto_local = env.auto_local(obj); |
| assert!(unwrap(env.is_same_object(&orig_obj, auto_local), &env)); |
| } |
| |
| #[test] |
| pub fn test_null_get_string() { |
| let mut env = attach_current_thread(); |
| let s = unsafe { JString::from_raw(std::ptr::null_mut() as _) }; |
| let ret = env.get_string(&s); |
| assert!(ret.is_err()); |
| } |
| |
| #[test] |
| pub fn test_invalid_list_get_string() { |
| let mut env = attach_current_thread(); |
| |
| let class = env.find_class("java/util/List").unwrap(); |
| let class = JString::from(JObject::from(class)); |
| let class = env.auto_local(class); |
| |
| let ret = env.get_string(&class); |
| assert!(ret.is_err()); |
| } |
| |
| fn test_throwable_descriptor_with_default_type<'local, D>(env: &mut JNIEnv<'local>, descriptor: D) |
| where |
| D: Desc<'local, JThrowable<'local>>, |
| { |
| let result = descriptor.lookup(env); |
| assert!(result.is_ok()); |
| let exception = result.unwrap(); |
| let exception = exception.as_ref(); |
| |
| assert_exception_type(env, exception, RUNTIME_EXCEPTION_CLASS); |
| assert_exception_message(env, exception, TEST_EXCEPTION_MESSAGE); |
| } |
| |
| // Helper method that asserts that result is Error and the cause is JavaException. |
| fn assert_exception(res: &Result<jobject, Error>, expect_message: &str) { |
| assert!(res.is_err()); |
| assert!(res |
| .as_ref() |
| .map_err(|error| matches!(error, Error::JavaException)) |
| .expect_err(expect_message)); |
| } |
| |
| // Shortcut to `assert_pending_java_exception_detailed()` without checking for expected type and |
| // message of exception. |
| fn assert_pending_java_exception(env: &mut JNIEnv) { |
| assert_pending_java_exception_detailed(env, None, None) |
| } |
| |
| // Helper method that asserts there is a pending Java exception of `expected_type` with |
| // `expected_message` and clears it if any. |
| fn assert_pending_java_exception_detailed( |
| env: &mut JNIEnv, |
| expected_type: Option<&str>, |
| expected_message: Option<&str>, |
| ) { |
| assert!(env.exception_check().unwrap()); |
| let exception = env.exception_occurred().expect("Unable to get exception"); |
| env.exception_clear().unwrap(); |
| |
| if let Some(expected_type) = expected_type { |
| assert_exception_type(env, &exception, expected_type); |
| } |
| |
| if let Some(expected_message) = expected_message { |
| assert_exception_message(env, &exception, expected_message); |
| } |
| } |
| |
| // Asserts that exception is of `expected_type` type. |
| fn assert_exception_type(env: &mut JNIEnv, exception: &JThrowable, expected_type: &str) { |
| assert!(env.is_instance_of(exception, expected_type).unwrap()); |
| } |
| |
| // Asserts that exception's message is `expected_message`. |
| fn assert_exception_message(env: &mut JNIEnv, exception: &JThrowable, expected_message: &str) { |
| let message = env |
| .call_method(exception, "getMessage", "()Ljava/lang/String;", &[]) |
| .unwrap() |
| .l() |
| .unwrap(); |
| let msg_rust: String = env.get_string(&message.into()).unwrap().into(); |
| assert_eq!(msg_rust, expected_message); |
| } |