// Copyright 2024 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #![allow(unsafe_code, clippy::unwrap_used, clippy::expect_used, clippy::panic)] use jni::{ descriptors::Desc, objects::{JObject, JString}, signature::{Primitive, ReturnType}, sys::jint, JNIEnv, JavaVM, }; use std::error::Error; use std::sync::atomic::{AtomicBool, Ordering}; mod common; use common::foo_class::*; static NATIVE_METHOD_CALLED: AtomicBool = AtomicBool::new(false); static TEST_PANIC: AtomicBool = AtomicBool::new(false); #[pourover::jni_method( package = "com.example", class = "Foo", panic_returns = -1 )] extern "system" fn nativeReturnsInt<'local>( mut env: JNIEnv<'local>, this: JObject<'local>, n: jint, ) -> jint { NATIVE_METHOD_CALLED.store(true, Ordering::SeqCst); let foo_value = env .get_field_unchecked(&this, &FIELD, ReturnType::Primitive(Primitive::Int)) .unwrap() .i() .unwrap(); n * foo_value } #[pourover::jni_method( package = "com.example", class = "Foo", panic_returns = JObject::null().into(), )] extern "system" fn nativeReturnsObject<'local>( env: JNIEnv<'local>, _this: JObject<'local>, n: jint, ) -> JString<'local> { if TEST_PANIC.load(Ordering::SeqCst) { panic!("testing panic case"); } env.new_string(n.to_string()).unwrap() } #[test] fn can_call_native_method() -> Result<(), Box> { // Create the environment let vm = JavaVM::new( jni::InitArgsBuilder::new() .version(jni::JNIVersion::V8) .option("-Xcheck:jni") .build()?, )?; let mut env = vm.attach_current_thread()?; // Load `Foo.class` { let foo_class = compile_foo()?; let loaded_foo = env.define_class(CLASS_DESC, &JObject::null(), &foo_class)?; env.delete_local_ref(loaded_foo)?; } let obj_foo = { let method_id = CONSTRUCTOR.lookup(&mut env)?; let args = &[jni::sys::jvalue { i: 123 }]; // Safety: `args` must match the constructor arg count and types. unsafe { env.new_object_unchecked(CONSTRUCTOR.cls(), method_id, args) }? }; let obj_foo = env.auto_local(obj_foo); NATIVE_METHOD_CALLED.store(false, Ordering::SeqCst); // TODO: It would be better if the JVM was able to find the method on its own, // but, since we are using the invocation API to create the JVM, I haven't found a way to make // it visible to the JVM. Normally the methods are loaded dynamically with // `System.loadLibrary`, but in this case the symbols already exist in the executable, and I'm // unsure why the JVM cannot find them. I have tried compiling with // `-Zexport-executable-symbols` but that wasn't working for me. env.register_native_methods( &FOO, &[ jni::NativeMethod { name: "nativeReturnsInt".into(), sig: "(I)I".into(), fn_ptr: crate::nativeReturnsInt as *mut std::ffi::c_void, }, jni::NativeMethod { name: "nativeReturnsObject".into(), sig: "(I)Ljava/lang/String;".into(), fn_ptr: crate::nativeReturnsObject as *mut std::ffi::c_void, }, ], )?; let method_value = { let method_id = NATIVE_METHOD.lookup(&mut env)?; let args = &[jni::sys::jvalue { i: 3 }]; // Safety: `args` must match the method arg count and types. unsafe { env.call_method_unchecked( &obj_foo, method_id, ReturnType::Primitive(Primitive::Int), args, ) }? .i()? }; assert_eq!(369, method_value); assert!(NATIVE_METHOD_CALLED.load(Ordering::SeqCst)); TEST_PANIC.store(false, Ordering::SeqCst); let string_value: JString = { let method_id = NATIVE_OBJECT_METHOD.lookup(&mut env)?; let args = &[jni::sys::jvalue { i: 1234 }]; // Safety: `args` must match the method arg count and types. unsafe { env.call_method_unchecked(&obj_foo, method_id, ReturnType::Object, args) }? .l()? .into() }; assert_eq!("1234", env.get_string(&string_value)?.to_str()?); env.delete_local_ref(string_value)?; TEST_PANIC.store(true, Ordering::SeqCst); let string_value: JString = { let method_id = NATIVE_OBJECT_METHOD.lookup(&mut env)?; let args = &[jni::sys::jvalue { i: 1234 }]; // Safety: `args` must match the method arg count and types. unsafe { env.call_method_unchecked(&obj_foo, method_id, ReturnType::Object, args) }? .l()? .into() }; assert!(string_value.is_null()); Ok(()) }