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}