use std::{
cell::Cell,
os::raw::c_char,
ptr,
sync::atomic::{AtomicUsize, Ordering},
thread::{Thread, current},
};
use log::debug;
use crate::{
JNIVersion,
env::Env,
errors::*,
objects::{Global, JObject, JThrowable, Reference},
strings::{JNIStr, JNIString},
sys,
};
#[cfg(all(feature = "invocation", not(target_os = "android")))]
use std::{os::raw::c_void, path::PathBuf};
#[cfg(feature = "invocation")]
use {crate::InitArgs, std::ffi::OsStr};
#[cfg(use_fls_attach_guard)]
use super::fls_attach_guard;
#[cfg(use_tls_attach_guard)]
use super::tls_attach_guard;
#[cfg(doc)]
use {
crate::env::{self, EnvUnowned},
crate::objects,
};
pub const DEFAULT_LOCAL_FRAME_CAPACITY: usize = 32;
static JAVA_VM_SINGLETON: std::sync::OnceLock<JavaVM> = std::sync::OnceLock::new();
#[derive(Debug, Clone)]
pub struct JavaVM(*mut sys::JavaVM);
unsafe impl Send for JavaVM {}
unsafe impl Sync for JavaVM {}
impl JavaVM {
#[cfg(feature = "invocation")]
pub fn new(args: InitArgs) -> StartJvmResult<Self> {
#[cfg(not(target_os = "android"))]
{
Self::with_libjvm(args, || {
Ok([
java_locator::locate_jvm_dyn_library()
.map_err(StartJvmError::NotFound)?
.as_str(),
java_locator::get_jvm_dyn_lib_file_name(),
]
.iter()
.collect::<PathBuf>())
})
}
#[cfg(target_os = "android")]
{
let _a = args;
Err(StartJvmError::Unsupported)
}
}
pub fn singleton() -> Result<Self> {
JAVA_VM_SINGLETON
.get()
.cloned()
.ok_or(Error::UninitializedJavaVM)
}
#[cfg(all(feature = "invocation", not(target_os = "android")))]
unsafe fn with_create_fn_ptr(
args: InitArgs,
create_fn_ptr: unsafe extern "system" fn(
pvm: *mut *mut sys::JavaVM,
penv: *mut *mut c_void,
args: *mut c_void,
) -> sys::jint,
) -> Result<Self> {
let mut ptr: *mut sys::JavaVM = ::std::ptr::null_mut();
let mut env: *mut sys::JNIEnv = ::std::ptr::null_mut();
unsafe {
jni_error_code_to_result(create_fn_ptr(
&mut ptr as *mut _,
&mut env as *mut *mut sys::JNIEnv as *mut *mut c_void,
args.inner_ptr(),
))?;
}
let vm = unsafe { Self::from_raw(ptr) };
#[cfg(use_tls_attach_guard)]
unsafe {
tls_attach_guard::tls_attach_current_thread(&vm, &Default::default())?
};
#[cfg(use_fls_attach_guard)]
unsafe {
fls_attach_guard::fls_attach_current_thread(&vm, &Default::default())?
};
Ok(vm)
}
#[cfg(all(feature = "invocation", not(target_os = "android")))]
fn impl_with_libjvm<P: AsRef<OsStr>>(
args: InitArgs,
libjvm_path: impl FnOnce() -> StartJvmResult<P>,
) -> StartJvmResult<Self> {
if let Some(jvm) = JAVA_VM_SINGLETON.get() {
Ok(jvm.clone())
} else {
let libjvm_path = libjvm_path()?;
let libjvm_path_string = libjvm_path.as_ref().to_string_lossy().into_owned();
let libjvm = match unsafe { libloading::Library::new(libjvm_path.as_ref()) } {
Ok(ok) => ok,
Err(error) => return Err(StartJvmError::LoadError(libjvm_path_string, error)),
};
let result = unsafe {
let create_fn = libjvm.get(b"JNI_CreateJavaVM\0").map_err(|error| {
StartJvmError::LoadError(libjvm_path_string.to_owned(), error)
})?;
Self::with_create_fn_ptr(args, *create_fn).map_err(StartJvmError::Create)
};
if result.is_ok() {
std::mem::forget(libjvm);
}
result
}
}
#[cfg(feature = "invocation")]
pub fn with_libjvm<P: AsRef<OsStr>>(
args: InitArgs,
libjvm_path: impl FnOnce() -> StartJvmResult<P>,
) -> StartJvmResult<Self> {
#[cfg(not(target_os = "android"))]
{
Self::impl_with_libjvm(args, libjvm_path)
}
#[cfg(target_os = "android")]
{
let _args = args;
let _libjvm_path = libjvm_path;
Err(StartJvmError::Unsupported)
}
}
pub unsafe fn from_raw(ptr: *mut sys::JavaVM) -> Self {
assert!(!ptr.is_null());
JAVA_VM_SINGLETON.get_or_init(|| JavaVM(ptr)).clone()
}
pub fn get_raw(&self) -> *mut sys::JavaVM {
self.0
}
pub(crate) fn from_env(env: &Env) -> Result<Self> {
if let Some(jvm) = JAVA_VM_SINGLETON.get() {
Ok(jvm.clone())
} else {
let mut raw = ptr::null_mut();
let res = unsafe { jni_call_no_post_check_ex!(env, v1_1, GetJavaVM, &mut raw)? };
jni_error_code_to_result(res)?;
unsafe { Ok(JavaVM::from_raw(raw)) }
}
}
pub fn attach_current_thread<F, T, E>(&self, callback: F) -> std::result::Result<T, E>
where
F: FnOnce(&mut Env) -> std::result::Result<T, E>,
E: From<Error>,
{
self.attach_current_thread_with_config(
AttachConfig::default,
Some(DEFAULT_LOCAL_FRAME_CAPACITY),
callback,
)
}
pub fn attach_current_thread_for_scope<F, T, E>(&self, callback: F) -> std::result::Result<T, E>
where
F: FnOnce(&mut Env) -> std::result::Result<T, E>,
E: From<Error>,
{
self.attach_current_thread_with_config(
|| AttachConfig::new().scoped(true),
Some(DEFAULT_LOCAL_FRAME_CAPACITY),
callback,
)
}
pub fn attach_current_thread_with_config<'config, F, C, T, E>(
&self,
config: C,
capacity: Option<usize>,
callback: F,
) -> std::result::Result<T, E>
where
F: FnOnce(&mut Env) -> std::result::Result<T, E>,
E: From<Error>,
C: FnOnce() -> AttachConfig<'config>,
{
let config = config();
let exceptions_policy = config.exceptions_policy;
let mut scope = ScopeToken::default();
let mut guard = unsafe { self.attach_current_thread_guard(|| config, &mut scope)? };
let env = guard.borrow_env_mut();
let pre_pending_exception = match exceptions_policy {
AttachmentExceptionPolicy::Ignore => None,
AttachmentExceptionPolicy::PreCheckPostCatch => {
if env.exception_check() {
return Err(Error::JavaException.into());
} else {
None
}
}
AttachmentExceptionPolicy::PreReThrowPostCatch => {
if let Some(e) = env.exception_occurred() {
env.exception_clear();
Some(e)
} else {
None
}
}
};
let ret = if let Some(capacity) = capacity {
env.with_local_frame(capacity, callback)
} else {
callback(env)
};
if exceptions_policy == AttachmentExceptionPolicy::Ignore {
guard.detach()?;
} else {
guard.detach_with_catch_and_throw(pre_pending_exception)?;
}
ret
}
pub unsafe fn attach_current_thread_guard<'config, 'scope, F>(
&self,
config: F,
_scope: &'scope mut ScopeToken,
) -> Result<AttachGuard<'scope>>
where
F: FnOnce() -> AttachConfig<'config>,
{
let guard = unsafe {
if let Some(guard) = Self::try_get_nested_env_attach_guard::<'scope>() {
guard
} else {
match self.sys_get_env_attachment() {
Ok(guard) => guard,
Err(Error::JniCall(JniError::ThreadDetached)) => {
let config = config();
if config.scoped {
let jni = sys_attach_current_thread(self, &config, ¤t(), true)?;
AttachGuard::from_owned(jni)
} else {
#[cfg(use_tls_attach_guard)]
{
tls_attach_guard::tls_attach_current_thread(self, &config)?
}
#[cfg(use_fls_attach_guard)]
{
fls_attach_guard::fls_attach_current_thread(self, &config)?
}
}
}
Err(err) => Err(err)?,
}
}
};
Ok(guard)
}
pub fn detach_current_thread(&self) -> Result<()> {
if THREAD_GUARD_NEST_LEVEL.get() != 0 {
return Err(Error::ThreadAttachmentGuarded);
}
#[cfg(use_tls_attach_guard)]
{
tls_attach_guard::tls_detach_current_thread()
}
#[cfg(use_fls_attach_guard)]
{
fls_attach_guard::fls_detach_current_thread()
}
}
#[doc(hidden)]
pub fn threads_attached(&self) -> usize {
ATTACHED_THREADS.load(Ordering::SeqCst)
}
#[doc(hidden)]
pub fn thread_attach_guard_level() -> usize {
THREAD_GUARD_NEST_LEVEL.get()
}
pub(crate) unsafe fn sys_get_env_attachment<'local>(&self) -> Result<AttachGuard<'local>> {
unsafe {
let mut ptr = ptr::null_mut();
let res =
java_vm_call_unchecked!(self, v1_2, GetEnv, &mut ptr, JNIVersion::V1_4.into());
jni_error_code_to_result(res)?;
let jni = ptr as *mut sys::JNIEnv;
Ok(AttachGuard::from_unowned(jni))
}
}
pub fn is_thread_attached(&self) -> Result<bool> {
unsafe {
self.sys_get_env_attachment()
.map(|_| true)
.or_else(|jni_err| match jni_err {
Error::JniCall(JniError::ThreadDetached) => Ok(false),
_ => Err(jni_err),
})
}
}
pub(crate) unsafe fn try_get_nested_env_attach_guard<'local>() -> Option<AttachGuard<'local>> {
let env_ptr = THREAD_ATTACHMENT.get();
if env_ptr.is_null() {
None
} else {
unsafe { Some(AttachGuard::from_unowned(env_ptr)) }
}
}
pub unsafe fn get_env_attachment<'scope>(
&self,
_scope: &'scope mut ScopeToken,
) -> Result<AttachGuard<'scope>> {
unsafe {
match Self::try_get_nested_env_attach_guard() {
Some(guard) => Ok(guard),
None => self.sys_get_env_attachment(),
}
}
}
#[doc(hidden)]
#[deprecated(
since = "0.22.0",
note = "use JavaVM::with_top_local_frame, JavaVM::attach_current_thread or JavaVM::get_env_attachment instead"
)]
pub unsafe fn get_env<'scope>(
&self,
_scope: &'scope mut ScopeToken,
) -> Result<AttachGuard<'scope>> {
unsafe { self.get_env_attachment(_scope) }
}
pub fn with_local_frame<F, T, E>(&self, capacity: usize, f: F) -> std::result::Result<T, E>
where
F: FnOnce(&mut Env) -> std::result::Result<T, E>,
E: From<Error>,
{
unsafe {
let mut scope = ScopeToken::default();
let mut guard = self.get_env_attachment(&mut scope)?;
guard.borrow_env_mut().with_local_frame(capacity, f)
}
}
pub fn with_top_local_frame<F, T, E>(&self, f: F) -> std::result::Result<T, E>
where
F: FnOnce(&mut Env) -> std::result::Result<T, E>,
E: From<Error>,
{
unsafe {
let mut scope = ScopeToken::default();
let mut guard = self.get_env_attachment(&mut scope)?;
f(guard.borrow_env_mut())
}
}
pub unsafe fn destroy(&self) -> Result<()> {
unsafe {
let res = java_vm_call_unchecked!(self, v1_1, DestroyJavaVM);
jni_error_code_to_result(res)
}
}
}
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)]
pub enum AttachmentExceptionPolicy {
Ignore,
#[default]
PreReThrowPostCatch,
PreCheckPostCatch,
}
#[derive(Debug, Default)]
pub struct AttachConfig<'a> {
scoped: bool,
exceptions_policy: AttachmentExceptionPolicy,
deprecated_thread_name: Option<JNIString>,
thread_name: Option<&'a JNIStr>,
group: Option<&'a Global<JObject<'static>>>,
}
impl<'a> AttachConfig<'a> {
pub fn new() -> Self {
Self::default()
}
pub fn scoped(mut self, scoped: bool) -> Self {
self.scoped = scoped;
self
}
pub fn exceptions_policy(mut self, policy: AttachmentExceptionPolicy) -> Self {
self.exceptions_policy = policy;
self
}
#[deprecated(since = "0.22.2", note = "Use `thread_name` instead")]
pub fn name<S: AsRef<str>>(mut self, name: S) -> Self {
self.deprecated_thread_name = Some(JNIString::from(name.as_ref()));
self
}
pub fn thread_name(mut self, name: &'a JNIStr) -> Self {
self.thread_name = Some(name);
self
}
pub fn group(mut self, group: &'a Global<JObject<'static>>) -> Self {
self.group = Some(group);
self
}
}
pub(super) static ATTACHED_THREADS: AtomicUsize = AtomicUsize::new(0);
pub(super) unsafe fn sys_attach_current_thread(
vm: &JavaVM,
config: &AttachConfig,
thread: &Thread,
inc_attached_count: bool,
) -> Result<*mut sys::JNIEnv> {
assert_eq!(JavaVM::thread_attach_guard_level(), 0);
let mut env_ptr = ptr::null_mut();
let mut args = sys::JavaVMAttachArgs {
version: JNIVersion::V1_4.into(),
name: config
.thread_name
.map(|s| s.as_ptr() as *mut c_char)
.unwrap_or_else(|| {
config
.deprecated_thread_name
.as_ref()
.map(|s| s.as_ptr() as *mut c_char)
.unwrap_or(ptr::null_mut())
}),
group: config
.group
.as_ref()
.map(|g| g.as_raw())
.unwrap_or(ptr::null_mut()),
};
let res = unsafe {
java_vm_call_unchecked!(
vm,
v1_1,
AttachCurrentThread,
&mut env_ptr,
&mut args as *mut sys::JavaVMAttachArgs as *mut core::ffi::c_void
)
};
jni_error_code_to_result(res)?;
if inc_attached_count {
ATTACHED_THREADS.fetch_add(1, Ordering::SeqCst);
}
debug!(
"Attached thread {} ({:?}). {} threads attached",
thread.name().unwrap_or_default(),
thread.id(),
ATTACHED_THREADS.load(Ordering::SeqCst)
);
Ok(env_ptr as *mut sys::JNIEnv)
}
#[cfg(target_os = "windows")]
fn windows_is_shutdown_in_progress() -> bool {
#[link(name = "ntdll")]
unsafe extern "system" {
fn RtlDllShutdownInProgress() -> u8; }
unsafe { RtlDllShutdownInProgress() != 0 }
}
pub(super) unsafe fn sys_detach_current_thread(
cross_check_env: Option<*mut jni_sys::JNIEnv>,
thread: &Thread,
) -> Result<()> {
assert_eq!(JavaVM::thread_attach_guard_level(), 0);
let vm = JavaVM::singleton()?;
#[cfg(target_os = "windows")]
let allow_jni = !windows_is_shutdown_in_progress();
#[cfg(not(target_os = "windows"))]
let allow_jni = true;
if allow_jni {
let was_attached = match unsafe { vm.sys_get_env_attachment() } {
Ok(mut guard) => {
if let Some(expected_env) = cross_check_env {
assert_eq!(
guard.env.raw, expected_env,
"BUG: Something meddled with the JNI attachment behind our back"
);
}
guard.borrow_env_mut().exception_clear();
drop(guard);
true
}
Err(Error::JniCall(JniError::ThreadDetached)) => {
assert!(
cross_check_env.is_none(),
"BUG: Thread was detached by external code"
);
false
}
Err(e) => return Err(e),
};
if was_attached {
unsafe {
java_vm_call_unchecked!(vm, v1_1, DetachCurrentThread);
}
debug!(
"Detached thread {} ({:?})",
thread.name().unwrap_or_default(),
thread.id()
);
} else {
debug!(
"Thread {} ({:?}) already detached (skipping DetachCurrentThread)",
thread.name().unwrap_or_default(),
thread.id()
);
}
}
ATTACHED_THREADS.fetch_sub(1, Ordering::SeqCst);
debug!(
"{} attachments remain (logical count)",
ATTACHED_THREADS.load(Ordering::SeqCst)
);
Ok(())
}
thread_local! {
#[cfg_attr(target_os = "android", allow(clippy::missing_const_for_thread_local))]
static THREAD_ATTACHMENT: Cell<*mut jni_sys::JNIEnv> = const { Cell::new(std::ptr::null_mut()) };
#[cfg_attr(target_os = "android", allow(clippy::missing_const_for_thread_local))]
pub(super) static THREAD_GUARD_NEST_LEVEL: Cell<usize> = const { Cell::new(0) };
}
#[derive(Debug)]
pub struct AttachGuard<'local> {
env: Env<'local>,
}
fn thread_guard_level_push(env: *mut jni_sys::JNIEnv) -> usize {
THREAD_GUARD_NEST_LEVEL.with(|cell| {
let level = cell.get();
if level == 0 {
THREAD_ATTACHMENT.set(env);
}
cell.set(level + 1);
level + 1
})
}
fn thread_guard_level_pop() -> usize {
let level = THREAD_GUARD_NEST_LEVEL.with(|cell| {
let level = cell.get();
assert_ne!(
level, 0,
"Spuriously dropped more AttachGuards than were known to exist"
);
cell.set(level - 1);
level - 1
});
if level == 0 {
THREAD_ATTACHMENT.set(std::ptr::null_mut());
}
level
}
impl<'local> AttachGuard<'local> {
unsafe fn from_owned(env: *mut sys::JNIEnv) -> Self {
let level = thread_guard_level_push(env);
let guard = Self {
env: unsafe { Env::new(env, level, true) },
};
let _vm = guard.env.get_java_vm();
guard
}
pub unsafe fn from_unowned(env: *mut sys::JNIEnv) -> Self {
let level = thread_guard_level_push(env);
let guard = Self {
env: unsafe { Env::new(env, level, false) },
};
let _vm = guard.env.get_java_vm();
guard
}
pub fn owns_attachment(&self) -> bool {
self.env.owns_attachment()
}
pub fn borrow_env_mut(&mut self) -> &mut Env<'local> {
self.env.assert_top();
&mut self.env
}
unsafe fn detach_impl(
&mut self,
catch_exceptions: bool,
throw: Option<JThrowable<'local>>,
) -> Result<()> {
let caught_exception = {
let env = self.borrow_env_mut();
let caught_exception = if catch_exceptions {
env.exception_catch()
} else {
Ok(())
};
if let Some(throwable) = throw {
let _ = env.throw(throwable);
}
caught_exception
};
let new_level = thread_guard_level_pop();
assert_eq!(
new_level + 1,
self.env.level,
"AttachGuard was dropped out-of-order with respect to other guards"
);
let detach_result = if self.owns_attachment() {
assert_eq!(
new_level, 0,
"Spurious AttachGuard that owns its attachment but is nested under another guard"
);
unsafe { sys_detach_current_thread(Some(self.env.raw), &std::thread::current()) }
} else {
Ok(())
};
match detach_result {
Err(err) => Err(err),
Ok(()) => caught_exception,
}
}
pub fn detach(mut self) -> Result<()> {
let res = unsafe { self.detach_impl(false, None) };
std::mem::forget(self);
res
}
pub fn detach_with_catch(mut self) -> Result<()> {
let res = unsafe { self.detach_impl(true, None) };
std::mem::forget(self);
res
}
pub fn detach_with_catch_and_throw(
mut self,
throwable: Option<JThrowable<'local>>,
) -> Result<()> {
let res = unsafe { self.detach_impl(true, throwable) };
std::mem::forget(self);
res
}
}
impl Drop for AttachGuard<'_> {
fn drop(&mut self) {
if let Err(err) = unsafe { self.detach_impl(false, None) } {
log::error!("Failed to detach current JNI thread: {err}");
}
}
}
#[derive(Debug, Default)]
pub struct ScopeToken {
_non_send_sync: std::marker::PhantomData<*const ()>,
}
#[cfg(test)]
mod test {
use crate::{AttachGuard, ScopeToken};
static_assertions::assert_not_impl_any!(ScopeToken: Send);
static_assertions::assert_not_impl_any!(ScopeToken: Sync);
static_assertions::assert_not_impl_any!(AttachGuard: Send);
static_assertions::assert_not_impl_any!(AttachGuard: Sync);
}