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