Vendor dependencies for 0.3.0 release

This commit is contained in:
2025-09-27 10:29:08 -05:00
parent 0c8d39d483
commit 82ab7f317b
26803 changed files with 16134934 additions and 0 deletions

211
vendor/jni/tests/executor.rs vendored Normal file
View File

@@ -0,0 +1,211 @@
#![cfg(feature = "invocation")]
use std::{
sync::{
atomic::{AtomicUsize, Ordering},
Arc, Barrier,
},
thread::spawn,
time::Duration,
};
use jni::{objects::AutoLocal, sys::jint, Executor};
use rusty_fork::rusty_fork_test;
mod util;
use util::{jvm, AtomicIntegerProxy};
#[test]
fn single_thread() {
let executor = Executor::new(jvm().clone());
test_single_thread(executor);
}
#[test]
fn serialized_threads() {
let executor = Executor::new(jvm().clone());
test_serialized_threads(executor);
}
#[test]
fn concurrent_threads() {
let executor = Executor::new(jvm().clone());
const THREAD_NUM: usize = 8;
test_concurrent_threads(executor, THREAD_NUM)
}
fn test_single_thread(executor: Executor) {
let mut atomic = AtomicIntegerProxy::new(executor, 0).unwrap();
assert_eq!(0, atomic.get().unwrap());
assert_eq!(1, atomic.increment_and_get().unwrap());
assert_eq!(3, atomic.add_and_get(2).unwrap());
assert_eq!(3, atomic.get().unwrap());
}
fn test_serialized_threads(executor: Executor) {
let mut atomic = AtomicIntegerProxy::new(executor, 0).unwrap();
assert_eq!(0, atomic.get().unwrap());
let jh = spawn(move || {
assert_eq!(1, atomic.increment_and_get().unwrap());
assert_eq!(3, atomic.add_and_get(2).unwrap());
atomic
});
let mut atomic = jh.join().unwrap();
assert_eq!(3, atomic.get().unwrap());
}
fn test_concurrent_threads(executor: Executor, thread_num: usize) {
const ITERS_PER_THREAD: usize = 10_000;
let mut atomic = AtomicIntegerProxy::new(executor, 0).unwrap();
let barrier = Arc::new(Barrier::new(thread_num));
let mut threads = Vec::new();
for _ in 0..thread_num {
let barrier = Arc::clone(&barrier);
let mut atomic = atomic.clone();
let jh = spawn(move || {
barrier.wait();
for _ in 0..ITERS_PER_THREAD {
atomic.increment_and_get().unwrap();
}
});
threads.push(jh);
}
for jh in threads {
jh.join().unwrap();
}
let expected = (ITERS_PER_THREAD * thread_num) as jint;
assert_eq!(expected, atomic.get().unwrap());
}
// We need to test `JavaVM::destroy()` in a separate process otherwise it will break
// all the other tests
rusty_fork_test! {
#[test]
fn test_destroy() {
const THREAD_NUM: usize = 2;
const DAEMON_THREAD_NUM: usize = 2;
static MATH_CLASS: &str = "java/lang/Math";
// We don't test this using an `Executor` because we don't want to
// attach all the threads as daemon threads.
let jvm = jvm().clone();
let atomic = Arc::new(AtomicUsize::new(0));
let attach_barrier = Arc::new(Barrier::new(THREAD_NUM + DAEMON_THREAD_NUM + 1));
let daemons_detached_barrier = Arc::new(Barrier::new(DAEMON_THREAD_NUM + 1));
let mut threads = Vec::new();
for _ in 0..THREAD_NUM {
let attach_barrier = Arc::clone(&attach_barrier);
let jvm = jvm.clone();
let atomic = atomic.clone();
let jh = spawn(move || {
let mut env = jvm.attach_current_thread().unwrap();
println!("java thread attach");
attach_barrier.wait();
println!("java thread run");
std::thread::sleep(Duration::from_millis(250));
println!("use before destroy...");
// Make some token JNI call
let _class = AutoLocal::new(env.find_class(MATH_CLASS).unwrap(), &env);
atomic.fetch_add(1, Ordering::SeqCst);
println!("java thread finished");
});
threads.push(jh);
}
for _ in 0..DAEMON_THREAD_NUM {
let attach_barrier = Arc::clone(&attach_barrier);
let daemons_detached_barrier = Arc::clone(&daemons_detached_barrier);
let jvm = jvm.clone();
let atomic = atomic.clone();
let jh = spawn(move || {
// We have to be _very_ careful to ensure we have finished accessing the
// JavaVM before it gets destroyed, including dropping the AutoLocal
// for the `MATH_CLASS`
{
let mut env = jvm.attach_current_thread_as_daemon().unwrap();
println!("daemon thread attach");
attach_barrier.wait();
println!("daemon thread run");
println!("daemon JVM use before destroy...");
let _class = AutoLocal::new(env.find_class(MATH_CLASS).unwrap(), &env);
}
// For it to be safe to call `JavaVM::destroy()` we need to ensure that
// daemon threads are detached from the JavaVM ahead of time because
// `JavaVM::destroy()` does not synchronize and wait for them to exit
// which means we would effectively trigger a use-after-free when daemon
// threads exit and they try to automatically detach from the `JavaVM`
//
// # Safety
// We won't be accessing any (invalid) `JNIEnv` once we have detached this
// thread
unsafe {
jvm.detach_current_thread();
}
daemons_detached_barrier.wait();
for _ in 0..10 {
std::thread::sleep(Duration::from_millis(100));
println!("daemon thread running");
}
atomic.fetch_add(1, Ordering::SeqCst);
println!("daemon thread finished");
});
threads.push(jh);
}
// At this point we at least know that all threads have been attached
// to the JVM
println!("MAIN: waiting for threads attached barrier");
attach_barrier.wait();
// Before we try and destroy the JavaVM we need to be sure that the daemon
// threads have finished using the VM since `jvm.destroy()` won't wait
// for daemon threads to exit.
println!("MAIN: waiting for daemon threads detached barrier");
daemons_detached_barrier.wait();
// # Safety
//
// We drop the `jvm` variable immediately after `destroy()` returns to avoid
// any use-after-free.
unsafe {
println!("MAIN: calling DestroyJavaVM()...");
jvm.destroy().unwrap();
drop(jvm);
println!("MAIN: jvm destroyed");
}
println!("MAIN: joining (waiting for) all threads");
let mut joined = 0;
for jh in threads {
jh.join().unwrap();
joined += 1;
println!(
"joined {joined} threads, atomic = {}",
atomic.load(Ordering::SeqCst)
);
}
assert_eq!(
atomic.load(Ordering::SeqCst),
THREAD_NUM + DAEMON_THREAD_NUM
);
}
}

View File

@@ -0,0 +1,62 @@
#![cfg(feature = "invocation")]
use std::{sync::Arc, thread::spawn};
use jni::{
errors::{Error, JniError},
Executor, JavaVM,
};
mod util;
use util::jvm;
/// Checks if nested attaches are working properly and threads detach themselves
/// on exit.
#[test]
fn nested_attach() {
let executor = Executor::new(jvm().clone());
assert_eq!(jvm().threads_attached(), 0);
let thread = spawn(|| {
assert_eq!(jvm().threads_attached(), 0);
check_nested_attach(jvm(), executor);
assert_eq!(jvm().threads_attached(), 1);
});
thread.join().unwrap();
assert_eq!(jvm().threads_attached(), 0);
}
/// Checks if nested `with_attached` calls does not detach the thread before the outer-most
/// call is finished.
fn check_nested_attach(vm: &Arc<JavaVM>, executor: Executor) {
check_detached(vm);
executor
.with_attached::<_, _, Error>(|_| {
check_attached(vm);
executor.with_attached::<_, _, Error>(|_| {
check_attached(vm);
Ok(())
})?;
check_attached(vm);
Ok(())
})
.unwrap();
}
fn check_attached(vm: &JavaVM) {
assert!(is_attached(vm));
}
fn check_detached(vm: &JavaVM) {
assert!(!is_attached(vm));
}
fn is_attached(vm: &JavaVM) -> bool {
vm.get_env()
.map(|_| true)
.or_else(|jni_err| match jni_err {
Error::JniCall(JniError::ThreadDetached) => Ok(false),
_ => Err(jni_err),
})
.expect("An unexpected JNI error occurred")
}

View File

@@ -0,0 +1,43 @@
// This is a separate test program because it has to start a JVM with a specific option.
#![cfg(feature = "invocation")]
use jni::{objects::JString, InitArgsBuilder, JavaVM};
#[test]
fn invocation_character_encoding() {
let jvm_args = InitArgsBuilder::new()
.version(jni::JNIVersion::V8)
.option("-Xcheck:jni")
// U+00A0 NO-BREAK SPACE is the only non-ASCII character that's present in all parts of
// ISO 8859. This minimizes the chance of this test failing as a result of the character
// not being present in the platform default character encoding. This test will still fail
// on platforms where the default character encoding cannot represent a no-break space,
// such as GBK.
.option("-Dnbsp=\u{00a0}")
.build()
.unwrap_or_else(|e| panic!("{:#?}", e));
let jvm = JavaVM::new(jvm_args).unwrap_or_else(|e| panic!("{:#?}", e));
let mut env = jvm.attach_current_thread().unwrap();
let prop_name = env.new_string("nbsp").unwrap();
let prop_value: JString = env
.call_static_method(
"java/lang/System",
"getProperty",
"(Ljava/lang/String;)Ljava/lang/String;",
&[(&prop_name).into()],
)
.unwrap()
.l()
.unwrap()
.into();
let prop_value_str = env.get_string(&prop_value).unwrap();
let prop_value_str: &str = prop_value_str.to_str().unwrap();
assert_eq!("\u{00a0}", prop_value_str);
}

43
vendor/jni/tests/java_integers.rs vendored Normal file
View File

@@ -0,0 +1,43 @@
#![cfg(feature = "invocation")]
use jni::{errors::Error, objects::JValue};
mod util;
use util::{attach_current_thread, print_exception};
#[test]
fn test_java_integers() {
let mut env = attach_current_thread();
let array_length = 50;
for value in -10..10 {
env.with_local_frame(16, |env| -> Result<_, Error> {
let integer_value =
env.new_object("java/lang/Integer", "(I)V", &[JValue::Int(value)])?;
let values_array =
env.new_object_array(array_length, "java/lang/Integer", &integer_value)?;
let result = env
.call_static_method(
"java/util/Arrays",
"binarySearch",
"([Ljava/lang/Object;Ljava/lang/Object;)I",
&[
JValue::Object(&values_array),
JValue::Object(&integer_value),
],
)?
.i()?;
assert!(0 <= result && result < array_length);
Ok(())
})
.unwrap_or_else(|e| {
print_exception(&env);
panic!("{:#?}", e);
})
}
}

43
vendor/jni/tests/jmap.rs vendored Normal file
View File

@@ -0,0 +1,43 @@
#![cfg(feature = "invocation")]
use jni::objects::{JMap, JObject, JString};
mod util;
use util::{attach_current_thread, unwrap};
#[test]
pub fn jmap_push_and_iterate() {
let mut env = attach_current_thread();
let data = &["hello", "world", "from", "test"];
// Create a new map. Use LinkedHashMap to have predictable iteration order
let map_object = unwrap(env.new_object("java/util/LinkedHashMap", "()V", &[]), &env);
let map = unwrap(JMap::from_env(&mut env, &map_object), &env);
// Push all strings
unwrap(
data.iter().try_for_each(|s| {
env.new_string(s)
.map(JObject::from)
.and_then(|s| map.put(&mut env, &s, &s).map(|_| ()))
}),
&env,
);
// Collect the keys using the JMap iterator
let mut collected = Vec::new();
unwrap(
map.iter(&mut env).and_then(|mut iter| {
while let Some(e) = iter.next(&mut env)? {
let s = JString::from(e.0);
let s = env.get_string(&s)?;
collected.push(String::from(s));
}
Ok(())
}),
&env,
);
let orig = data.to_vec();
assert_eq!(orig, collected);
}

1191
vendor/jni/tests/jni_api.rs vendored Normal file

File diff suppressed because it is too large Load Diff

80
vendor/jni/tests/jni_global_refs.rs vendored Normal file
View File

@@ -0,0 +1,80 @@
#![cfg(feature = "invocation")]
use std::{
sync::{Arc, Barrier},
thread::spawn,
};
use jni::{
objects::{AutoLocal, JValue},
sys::jint,
};
mod util;
use util::{attach_current_thread, unwrap};
#[test]
pub fn global_ref_works_in_other_threads() {
const ITERS_PER_THREAD: usize = 10_000;
let mut env = attach_current_thread();
let mut join_handlers = Vec::new();
let atomic_integer = {
let local_ref = AutoLocal::new(
unwrap(
env.new_object(
"java/util/concurrent/atomic/AtomicInteger",
"(I)V",
&[JValue::from(0)],
),
&env,
),
&env,
);
unwrap(env.new_global_ref(&local_ref), &env)
};
// Test with a different number of threads (from 2 to 8)
for thread_num in 2..9 {
let barrier = Arc::new(Barrier::new(thread_num));
for _ in 0..thread_num {
let barrier = barrier.clone();
let atomic_integer = atomic_integer.clone();
let jh = spawn(move || {
let mut env = attach_current_thread();
barrier.wait();
for _ in 0..ITERS_PER_THREAD {
unwrap(
unwrap(
env.call_method(&atomic_integer, "incrementAndGet", "()I", &[]),
&env,
)
.i(),
&env,
);
}
});
join_handlers.push(jh);
}
for jh in join_handlers.drain(..) {
jh.join().unwrap();
}
let expected = (ITERS_PER_THREAD * thread_num) as jint;
assert_eq!(
expected,
unwrap(
unwrap(
env.call_method(&atomic_integer, "getAndSet", "(I)I", &[JValue::from(0)]),
&env,
)
.i(),
&env,
)
);
}
}

195
vendor/jni/tests/jni_weak_refs.rs vendored Normal file
View File

@@ -0,0 +1,195 @@
#![cfg(feature = "invocation")]
use std::{
sync::{Arc, Barrier},
thread::spawn,
};
use jni::{
objects::{AutoLocal, JValue},
sys::jint,
JNIEnv,
};
mod util;
use util::{attach_current_thread, unwrap};
#[test]
pub fn weak_ref_works_in_other_threads() {
const ITERS_PER_THREAD: usize = 10_000;
let mut env = attach_current_thread();
let mut join_handlers = Vec::new();
let atomic_integer_local = AutoLocal::new(
unwrap(
env.new_object(
"java/util/concurrent/atomic/AtomicInteger",
"(I)V",
&[JValue::from(0)],
),
&env,
),
&env,
);
let atomic_integer =
unwrap(env.new_weak_ref(&atomic_integer_local), &env).expect("weak ref should not be null");
// Test with a different number of threads (from 2 to 8)
for thread_num in 2..9 {
let barrier = Arc::new(Barrier::new(thread_num));
for _ in 0..thread_num {
let barrier = barrier.clone();
let atomic_integer = atomic_integer.clone();
let jh = spawn(move || {
let mut env = attach_current_thread();
barrier.wait();
for _ in 0..ITERS_PER_THREAD {
let atomic_integer = env.auto_local(
unwrap(atomic_integer.upgrade_local(&env), &env)
.expect("AtomicInteger shouldn't have been GC'd yet"),
);
unwrap(
unwrap(
env.call_method(&atomic_integer, "incrementAndGet", "()I", &[]),
&env,
)
.i(),
&env,
);
}
});
join_handlers.push(jh);
}
for jh in join_handlers.drain(..) {
jh.join().unwrap();
}
let expected = (ITERS_PER_THREAD * thread_num) as jint;
assert_eq!(
expected,
unwrap(
unwrap(
env.call_method(
&atomic_integer_local,
"getAndSet",
"(I)I",
&[JValue::from(0)]
),
&env,
)
.i(),
&env,
)
);
}
}
#[test]
fn weak_ref_is_actually_weak() {
let mut env = attach_current_thread();
// This test uses `with_local_frame` to work around issue #109.
fn run_gc(env: &mut JNIEnv) {
unwrap(
env.with_local_frame(1, |env| {
env.call_static_method("java/lang/System", "gc", "()V", &[])?;
Ok(())
}),
env,
);
}
for _ in 0..100 {
let obj_local = unwrap(
env.with_local_frame_returning_local(2, |env| {
env.new_object("java/lang/Object", "()V", &[])
}),
&env,
);
let obj_local = env.auto_local(obj_local);
let obj_weak =
unwrap(env.new_weak_ref(&obj_local), &env).expect("weak ref should not be null");
let obj_weak2 =
unwrap(obj_weak.clone_in_jvm(&env), &env).expect("weak ref should not be null");
run_gc(&mut env);
for obj_weak in &[&obj_weak, &obj_weak2] {
{
let obj_local_from_weak = env.auto_local(
unwrap(obj_weak.upgrade_local(&env), &env)
.expect("object shouldn't have been GC'd yet"),
);
assert!(!obj_local_from_weak.is_null());
assert!(unwrap(
env.is_same_object(&obj_local_from_weak, &obj_local),
&env,
));
}
{
let obj_global_from_weak = unwrap(obj_weak.upgrade_global(&env), &env)
.expect("object shouldn't have been GC'd yet");
assert!(!obj_global_from_weak.is_null());
assert!(unwrap(
env.is_same_object(&obj_global_from_weak, &obj_local),
&env,
));
}
assert!(unwrap(obj_weak.is_same_object(&env, &obj_local), &env));
assert!(
!unwrap(obj_weak.is_garbage_collected(&env), &env),
"`is_garbage_collected` returned incorrect value"
);
}
assert!(unwrap(
obj_weak.is_weak_ref_to_same_object(&env, &obj_weak2),
&env,
));
drop(obj_local);
run_gc(&mut env);
for obj_weak in &[&obj_weak, &obj_weak2] {
{
let obj_local_from_weak = unwrap(obj_weak.upgrade_local(&env), &env);
assert!(
obj_local_from_weak.is_none(),
"object should have been GC'd"
);
}
{
let obj_global_from_weak = unwrap(obj_weak.upgrade_global(&env), &env);
assert!(
obj_global_from_weak.is_none(),
"object should have been GC'd"
);
}
assert!(
unwrap(obj_weak.is_garbage_collected(&env), &env),
"`is_garbage_collected` returned incorrect value"
);
}
assert!(unwrap(
obj_weak.is_weak_ref_to_same_object(&env, &obj_weak2),
&env,
));
}
}

View File

@@ -0,0 +1,18 @@
#![cfg(feature = "invocation")]
mod util;
use util::{attach_current_thread, call_java_abs, jvm};
#[test]
fn thread_attach_guard_detaches_on_drop() {
assert_eq!(jvm().threads_attached(), 0);
{
let mut guard = attach_current_thread();
assert_eq!(jvm().threads_attached(), 1);
let val = call_java_abs(&mut guard, -1);
assert_eq!(val, 1);
}
assert_eq!(jvm().threads_attached(), 0);
// Verify that this thread is really detached.
assert!(jvm().get_env().is_err());
}

19
vendor/jni/tests/threads_detach.rs vendored Normal file
View File

@@ -0,0 +1,19 @@
#![cfg(feature = "invocation")]
use std::thread::spawn;
mod util;
use util::{attach_current_thread_permanently, call_java_abs, jvm};
#[test]
fn thread_detaches_when_finished() {
let thread = spawn(|| {
let mut env = attach_current_thread_permanently();
let val = call_java_abs(&mut env, -2);
assert_eq!(val, 2);
assert_eq!(jvm().threads_attached(), 1);
});
thread.join().unwrap();
assert_eq!(jvm().threads_attached(), 0);
}

View File

@@ -0,0 +1,19 @@
#![cfg(feature = "invocation")]
use std::thread::spawn;
mod util;
use util::{attach_current_thread_as_daemon, call_java_abs, jvm};
#[test]
fn daemon_thread_detaches_when_finished() {
let thread = spawn(|| {
let mut env = attach_current_thread_as_daemon();
let val = call_java_abs(&mut env, -3);
assert_eq!(val, 3);
assert_eq!(jvm().threads_attached(), 1);
});
thread.join().unwrap();
assert_eq!(jvm().threads_attached(), 0);
}

View File

@@ -0,0 +1,21 @@
#![cfg(feature = "invocation")]
mod util;
use util::{attach_current_thread, call_java_abs, detach_current_thread, jvm};
#[test]
pub fn explicit_detach_detaches_thread_attached_locally() {
assert_eq!(jvm().threads_attached(), 0);
let mut guard = attach_current_thread();
let val = call_java_abs(&mut guard, -1);
assert_eq!(val, 1);
assert_eq!(jvm().threads_attached(), 1);
// # Safety
// we won't be trying to use a pre-existing (invalid) `JNIEnv` after detaching
unsafe {
detach_current_thread();
}
assert_eq!(jvm().threads_attached(), 0);
assert!(jvm().get_env().is_err());
}

View File

@@ -0,0 +1,21 @@
#![cfg(feature = "invocation")]
mod util;
use util::{attach_current_thread_as_daemon, call_java_abs, detach_current_thread, jvm};
#[test]
pub fn explicit_detach_detaches_thread_attached_as_daemon() {
assert_eq!(jvm().threads_attached(), 0);
let mut guard = attach_current_thread_as_daemon();
let val = call_java_abs(&mut guard, -1);
assert_eq!(val, 1);
assert_eq!(jvm().threads_attached(), 1);
// # Safety
// we won't be trying to use a pre-existing (invalid) `JNIEnv` after detaching
unsafe {
detach_current_thread();
}
assert_eq!(jvm().threads_attached(), 0);
assert!(jvm().get_env().is_err());
}

View File

@@ -0,0 +1,21 @@
#![cfg(feature = "invocation")]
mod util;
use util::{attach_current_thread_permanently, call_java_abs, detach_current_thread, jvm};
#[test]
pub fn explicit_detach_detaches_thread_attached_permanently() {
assert_eq!(jvm().threads_attached(), 0);
let mut guard = attach_current_thread_permanently();
let val = call_java_abs(&mut guard, -1);
assert_eq!(val, 1);
assert_eq!(jvm().threads_attached(), 1);
// # Safety
// we won't be trying to use a pre-existing (invalid) `JNIEnv` after detaching
unsafe {
detach_current_thread();
}
assert_eq!(jvm().threads_attached(), 0);
assert!(jvm().get_env().is_err());
}

View File

@@ -0,0 +1,48 @@
#![cfg(feature = "invocation")]
mod util;
use util::{
attach_current_thread, attach_current_thread_as_daemon, attach_current_thread_permanently,
call_java_abs, jvm,
};
#[test]
pub fn nested_attaches_should_not_detach_daemon_thread() {
assert_eq!(jvm().threads_attached(), 0);
let mut env = attach_current_thread_as_daemon();
let val = call_java_abs(&mut env, -1);
assert_eq!(val, 1);
assert_eq!(jvm().threads_attached(), 1);
// Create nested AttachGuard.
{
let mut env_nested = attach_current_thread();
let val = call_java_abs(&mut env_nested, -2);
assert_eq!(val, 2);
assert_eq!(jvm().threads_attached(), 1);
}
// Call a Java method after nested guard has been dropped to check that
// this thread has not been detached.
let val = call_java_abs(&mut env, -3);
assert_eq!(val, 3);
assert_eq!(jvm().threads_attached(), 1);
// Nested attach_permanently is a no-op.
{
let mut env_nested = attach_current_thread_permanently();
let val = call_java_abs(&mut env_nested, -4);
assert_eq!(val, 4);
assert_eq!(jvm().threads_attached(), 1);
}
assert_eq!(jvm().threads_attached(), 1);
// Nested attach_as_daemon is a no-op.
{
let mut env_nested = attach_current_thread_as_daemon();
let val = call_java_abs(&mut env_nested, -5);
assert_eq!(val, 5);
assert_eq!(jvm().threads_attached(), 1);
}
assert_eq!(jvm().threads_attached(), 1);
}

View File

@@ -0,0 +1,54 @@
#![cfg(feature = "invocation")]
mod util;
use util::{
attach_current_thread, attach_current_thread_as_daemon, attach_current_thread_permanently,
call_java_abs, jvm,
};
#[test]
pub fn nested_attaches_should_not_detach_guarded_thread() {
assert_eq!(jvm().threads_attached(), 0);
let mut env = attach_current_thread();
let val = call_java_abs(&mut env, -1);
assert_eq!(val, 1);
assert_eq!(jvm().threads_attached(), 1);
// Create nested AttachGuard.
{
let mut env_nested = attach_current_thread();
let val = call_java_abs(&mut env_nested, -2);
assert_eq!(val, 2);
assert_eq!(jvm().threads_attached(), 1);
}
// Call a Java method after nested guard has been dropped to check that
// this thread has not been detached.
let val = call_java_abs(&mut env, -3);
assert_eq!(val, 3);
assert_eq!(jvm().threads_attached(), 1);
// Nested attach_permanently is a no-op.
{
let mut env_nested = attach_current_thread_permanently();
let val = call_java_abs(&mut env_nested, -4);
assert_eq!(val, 4);
assert_eq!(jvm().threads_attached(), 1);
}
assert_eq!(jvm().threads_attached(), 1);
// Nested attach_as_daemon is a no-op.
{
let mut env_nested = attach_current_thread_as_daemon();
let val = call_java_abs(&mut env_nested, -5);
assert_eq!(val, 5);
assert_eq!(jvm().threads_attached(), 1);
}
assert_eq!(jvm().threads_attached(), 1);
// Check that after guard is dropped the thread is properly detached
// despite nested "permanent" attaches.
drop(env);
assert_eq!(jvm().threads_attached(), 0);
assert!(jvm().get_env().is_err());
}

View File

@@ -0,0 +1,49 @@
#![cfg(feature = "invocation")]
mod util;
use util::{
attach_current_thread, attach_current_thread_as_daemon, attach_current_thread_permanently,
call_java_abs, jvm,
};
#[test]
pub fn nested_attaches_should_not_detach_permanent_thread() {
assert_eq!(jvm().threads_attached(), 0);
let mut env = attach_current_thread_permanently();
let val = call_java_abs(&mut env, -1);
assert_eq!(val, 1);
assert_eq!(jvm().threads_attached(), 1);
// Create nested AttachGuard.
{
let mut env_nested = attach_current_thread();
let val = call_java_abs(&mut env_nested, -2);
assert_eq!(val, 2);
assert_eq!(jvm().threads_attached(), 1);
}
// Call a Java method after nested guard has been dropped to check that
// this thread has not been detached.
let val = call_java_abs(&mut env, -3);
assert_eq!(val, 3);
assert_eq!(jvm().threads_attached(), 1);
// Nested attach_permanently is a no-op.
{
let mut env_nested = attach_current_thread_permanently();
let val = call_java_abs(&mut env_nested, -4);
assert_eq!(val, 4);
assert_eq!(jvm().threads_attached(), 1);
}
assert_eq!(jvm().threads_attached(), 1);
// Nested attach_as_daemon is a no-op.
{
let mut env_nested = attach_current_thread_as_daemon();
let val = call_java_abs(&mut env_nested, -5);
assert_eq!(val, 5);
assert_eq!(jvm().threads_attached(), 1);
}
assert_eq!(jvm().threads_attached(), 1);
assert!(jvm().get_env().is_ok());
}

53
vendor/jni/tests/util/example_proxy.rs vendored Normal file
View File

@@ -0,0 +1,53 @@
#![allow(dead_code)]
use jni::{
errors::*,
objects::{GlobalRef, JValue},
sys::jint,
Executor, JNIEnv,
};
/// A test example of a native-to-JNI proxy
#[derive(Clone)]
pub struct AtomicIntegerProxy {
exec: Executor,
obj: GlobalRef,
}
impl AtomicIntegerProxy {
/// Creates a new instance of `AtomicIntegerProxy`
pub fn new(exec: Executor, init_value: jint) -> Result<Self> {
let obj = exec.with_attached(|env: &mut JNIEnv| {
let i = env.new_object(
"java/util/concurrent/atomic/AtomicInteger",
"(I)V",
&[JValue::from(init_value)],
)?;
env.new_global_ref(i)
})?;
Ok(AtomicIntegerProxy { exec, obj })
}
/// Gets a current value from java object
pub fn get(&mut self) -> Result<jint> {
self.exec
.with_attached(|env| env.call_method(&self.obj, "get", "()I", &[])?.i())
}
/// Increments a value of java object and then gets it
pub fn increment_and_get(&mut self) -> Result<jint> {
self.exec.with_attached(|env| {
env.call_method(&self.obj, "incrementAndGet", "()I", &[])?
.i()
})
}
/// Adds some value to the value of java object and then gets a resulting value
pub fn add_and_get(&mut self, delta: jint) -> Result<jint> {
let delta = JValue::from(delta);
self.exec.with_attached(|env| {
env.call_method(&self.obj, "addAndGet", "(I)I", &[delta])?
.i()
})
}
}

85
vendor/jni/tests/util/mod.rs vendored Normal file
View File

@@ -0,0 +1,85 @@
use std::sync::{Arc, Once};
use jni::{
errors::Result, objects::JValue, sys::jint, AttachGuard, InitArgsBuilder, JNIEnv, JNIVersion,
JavaVM,
};
mod example_proxy;
pub use self::example_proxy::AtomicIntegerProxy;
pub fn jvm() -> &'static Arc<JavaVM> {
static mut JVM: Option<Arc<JavaVM>> = None;
static INIT: Once = Once::new();
INIT.call_once(|| {
let jvm_args = InitArgsBuilder::new()
.version(JNIVersion::V8)
.option("-Xcheck:jni")
.build()
.unwrap_or_else(|e| panic!("{:#?}", e));
let jvm = JavaVM::new(jvm_args).unwrap_or_else(|e| panic!("{:#?}", e));
unsafe {
JVM = Some(Arc::new(jvm));
}
});
unsafe { JVM.as_ref().unwrap() }
}
#[allow(dead_code)]
pub fn call_java_abs(env: &mut JNIEnv, value: i32) -> i32 {
env.call_static_method(
"java/lang/Math",
"abs",
"(I)I",
&[JValue::from(value as jint)],
)
.unwrap()
.i()
.unwrap()
}
#[allow(dead_code)]
pub fn attach_current_thread() -> AttachGuard<'static> {
jvm()
.attach_current_thread()
.expect("failed to attach jvm thread")
}
#[allow(dead_code)]
pub fn attach_current_thread_as_daemon() -> JNIEnv<'static> {
jvm()
.attach_current_thread_as_daemon()
.expect("failed to attach jvm daemon thread")
}
#[allow(dead_code)]
pub fn attach_current_thread_permanently() -> JNIEnv<'static> {
jvm()
.attach_current_thread_permanently()
.expect("failed to attach jvm thread permanently")
}
#[allow(dead_code)]
pub unsafe fn detach_current_thread() {
jvm().detach_current_thread()
}
pub fn print_exception(env: &JNIEnv) {
let exception_occurred = env.exception_check().unwrap_or_else(|e| panic!("{:?}", e));
if exception_occurred {
env.exception_describe()
.unwrap_or_else(|e| panic!("{:?}", e));
}
}
#[allow(dead_code)]
pub fn unwrap<T>(res: Result<T>, env: &JNIEnv) -> T {
res.unwrap_or_else(|e| {
print_exception(env);
panic!("{:#?}", e);
})
}