java_oxide/
vm.rs

1use crate::Env;
2use jni_sys::*;
3use std::{
4    cell::{Cell, OnceCell},
5    ptr::null_mut,
6};
7
8/// FFI: Use **&VM** instead of `*const JavaVM`.  This represents a global, process-wide Java exection environment.
9///
10/// On Android, there is only one VM per-process, although on desktop it's possible (if rare) to have multiple VMs
11/// within the same process.  This library does not support having multiple VMs active simultaniously.
12///
13/// This is a "safe" alternative to `jni_sys::JavaVM` raw pointers, with the following caveats:
14///
15/// 1)  A null vm will result in **undefined behavior**.  Java should not be invoking your native functions with a null
16///     `*mut JavaVM`, however, so I don't believe this is a problem in practice unless you've bindgened the C header
17///     definitions elsewhere, calling them (requiring `unsafe`), and passing null pointers (generally UB for JNI
18///     functions anyways, so can be seen as a caller soundness issue.)
19///
20/// 2)  Allowing the underlying JavaVM to be modified is **undefined behavior**.  I don't believe the JNI libraries
21///     modify the JavaVM, so as long as you're not accepting a `*mut JavaVM` elsewhere, using unsafe to dereference it,
22///     and mucking with the methods on it yourself, I believe this "should" be fine.
23#[repr(transparent)]
24#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
25pub struct VM(*mut JavaVM);
26
27impl VM {
28    pub fn as_raw(&self) -> *mut JavaVM {
29        self.0
30    }
31
32    /// Constructs `VM` with a valid `jni_sys::JavaVM` raw pointer.
33    ///
34    /// # Safety
35    ///
36    /// - Make sure the corresponding JVM will keep alive within the lifetime of current native library or application.
37    /// - Do not use any class redefinition feature, which may break the validity of method/field IDs to be cached.
38    pub unsafe fn from_raw(vm: *mut JavaVM) -> Self {
39        Self(vm)
40    }
41
42    pub fn with_env<F, R>(&self, callback: F) -> R
43    where
44        F: for<'env> FnOnce(Env<'env>) -> R,
45    {
46        let mut env = null_mut();
47        let just_attached = match unsafe {
48            ((**self.0).v1_2.GetEnv)(self.0, &mut env, JNI_VERSION_1_2)
49        } {
50            JNI_OK => false,
51            JNI_EDETACHED => {
52                let ret =
53                    unsafe { ((**self.0).v1_2.AttachCurrentThread)(self.0, &mut env, null_mut()) };
54                if ret != JNI_OK {
55                    panic!("AttachCurrentThread returned unknown error: {ret}")
56                }
57                if !get_thread_exit_flag() {
58                    set_thread_attach_flag(self.0);
59                }
60                true
61            }
62            JNI_EVERSION => panic!("GetEnv returned JNI_EVERSION"),
63            unexpected => panic!("GetEnv returned unknown error: {unexpected}"),
64        };
65
66        let result = callback(unsafe { Env::from_raw(env as _) });
67
68        if just_attached && get_thread_exit_flag() {
69            // this is needed in case of `with_env` is used on dropping some thread-local instance.
70            unsafe { ((**self.0).v1_2.DetachCurrentThread)(self.0) };
71        }
72
73        result
74    }
75}
76
77unsafe impl Send for VM {}
78unsafe impl Sync for VM {}
79
80thread_local! {
81    static THREAD_ATTACH_FLAG: Cell<Option<AttachFlag>> = const { Cell::new(None) };
82    static THREAD_EXIT_FLAG: OnceCell<()> = const { OnceCell::new() };
83}
84
85struct AttachFlag {
86    raw_vm: *mut JavaVM,
87}
88
89impl Drop for AttachFlag {
90    fn drop(&mut self) {
91        // avoids the fatal error "Native thread exiting without having called DetachCurrentThread"
92        unsafe { ((**self.raw_vm).v1_2.DetachCurrentThread)(self.raw_vm) };
93        let _ = THREAD_EXIT_FLAG.try_with(|flag| flag.set(()));
94    }
95}
96
97fn set_thread_attach_flag(raw_vm: *mut JavaVM) {
98    THREAD_ATTACH_FLAG.replace(Some(AttachFlag { raw_vm }));
99}
100
101fn get_thread_exit_flag() -> bool {
102    THREAD_EXIT_FLAG
103        .try_with(|flag| flag.get().is_some())
104        .unwrap_or(true)
105}