1 // Copyright 2024 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 #![allow(unsafe_code, clippy::unwrap_used, clippy::expect_used, clippy::panic)]
16 
17 use jni::{
18     descriptors::Desc,
19     objects::{JObject, JString},
20     signature::{Primitive, ReturnType},
21     sys::jint,
22     JNIEnv, JavaVM,
23 };
24 use std::error::Error;
25 use std::sync::atomic::{AtomicBool, Ordering};
26 
27 mod common;
28 use common::foo_class::*;
29 
30 static NATIVE_METHOD_CALLED: AtomicBool = AtomicBool::new(false);
31 static TEST_PANIC: AtomicBool = AtomicBool::new(false);
32 
33 #[pourover::jni_method(
34     package = "com.example",
35     class = "Foo",
36     panic_returns = -1
37 )]
nativeReturnsInt<'local>( mut env: JNIEnv<'local>, this: JObject<'local>, n: jint, ) -> jint38 extern "system" fn nativeReturnsInt<'local>(
39     mut env: JNIEnv<'local>,
40     this: JObject<'local>,
41     n: jint,
42 ) -> jint {
43     NATIVE_METHOD_CALLED.store(true, Ordering::SeqCst);
44 
45     let foo_value = env
46         .get_field_unchecked(&this, &FIELD, ReturnType::Primitive(Primitive::Int))
47         .unwrap()
48         .i()
49         .unwrap();
50 
51     n * foo_value
52 }
53 
54 #[pourover::jni_method(
55     package = "com.example",
56     class = "Foo",
57     panic_returns = JObject::null().into(),
58 )]
nativeReturnsObject<'local>( env: JNIEnv<'local>, _this: JObject<'local>, n: jint, ) -> JString<'local>59 extern "system" fn nativeReturnsObject<'local>(
60     env: JNIEnv<'local>,
61     _this: JObject<'local>,
62     n: jint,
63 ) -> JString<'local> {
64     if TEST_PANIC.load(Ordering::SeqCst) {
65         panic!("testing panic case");
66     }
67     env.new_string(n.to_string()).unwrap()
68 }
69 
70 #[test]
can_call_native_method() -> Result<(), Box<dyn Error>>71 fn can_call_native_method() -> Result<(), Box<dyn Error>> {
72     // Create the environment
73     let vm = JavaVM::new(
74         jni::InitArgsBuilder::new()
75             .version(jni::JNIVersion::V8)
76             .option("-Xcheck:jni")
77             .build()?,
78     )?;
79     let mut env = vm.attach_current_thread()?;
80 
81     // Load `Foo.class`
82     {
83         let foo_class = compile_foo()?;
84         let loaded_foo = env.define_class(CLASS_DESC, &JObject::null(), &foo_class)?;
85         env.delete_local_ref(loaded_foo)?;
86     }
87 
88     let obj_foo = {
89         let method_id = CONSTRUCTOR.lookup(&mut env)?;
90         let args = &[jni::sys::jvalue { i: 123 }];
91         // Safety: `args` must match the constructor arg count and types.
92         unsafe { env.new_object_unchecked(CONSTRUCTOR.cls(), method_id, args) }?
93     };
94     let obj_foo = env.auto_local(obj_foo);
95 
96     NATIVE_METHOD_CALLED.store(false, Ordering::SeqCst);
97 
98     // TODO: It would be better if the JVM was able to find the method on its own,
99     // but, since we are using the invocation API to create the JVM, I haven't found a way to make
100     // it visible to the JVM. Normally the methods are loaded dynamically with
101     // `System.loadLibrary`, but in this case the symbols already exist in the executable, and I'm
102     // unsure why the JVM cannot find them. I have tried compiling with
103     // `-Zexport-executable-symbols` but that wasn't working for me.
104     env.register_native_methods(
105         &FOO,
106         &[
107             jni::NativeMethod {
108                 name: "nativeReturnsInt".into(),
109                 sig: "(I)I".into(),
110                 fn_ptr: crate::nativeReturnsInt as *mut std::ffi::c_void,
111             },
112             jni::NativeMethod {
113                 name: "nativeReturnsObject".into(),
114                 sig: "(I)Ljava/lang/String;".into(),
115                 fn_ptr: crate::nativeReturnsObject as *mut std::ffi::c_void,
116             },
117         ],
118     )?;
119 
120     let method_value = {
121         let method_id = NATIVE_METHOD.lookup(&mut env)?;
122         let args = &[jni::sys::jvalue { i: 3 }];
123         // Safety: `args` must match the method arg count and types.
124         unsafe {
125             env.call_method_unchecked(
126                 &obj_foo,
127                 method_id,
128                 ReturnType::Primitive(Primitive::Int),
129                 args,
130             )
131         }?
132         .i()?
133     };
134 
135     assert_eq!(369, method_value);
136     assert!(NATIVE_METHOD_CALLED.load(Ordering::SeqCst));
137 
138     TEST_PANIC.store(false, Ordering::SeqCst);
139     let string_value: JString = {
140         let method_id = NATIVE_OBJECT_METHOD.lookup(&mut env)?;
141         let args = &[jni::sys::jvalue { i: 1234 }];
142         // Safety: `args` must match the method arg count and types.
143         unsafe { env.call_method_unchecked(&obj_foo, method_id, ReturnType::Object, args) }?
144             .l()?
145             .into()
146     };
147     assert_eq!("1234", env.get_string(&string_value)?.to_str()?);
148     env.delete_local_ref(string_value)?;
149 
150     TEST_PANIC.store(true, Ordering::SeqCst);
151     let string_value: JString = {
152         let method_id = NATIVE_OBJECT_METHOD.lookup(&mut env)?;
153         let args = &[jni::sys::jvalue { i: 1234 }];
154         // Safety: `args` must match the method arg count and types.
155         unsafe { env.call_method_unchecked(&obj_foo, method_id, ReturnType::Object, args) }?
156             .l()?
157             .into()
158     };
159     assert!(string_value.is_null());
160 
161     Ok(())
162 }
163