diff --git a/java_runtime/src/classes/java/lang/thread.rs b/java_runtime/src/classes/java/lang/thread.rs index 91e2ea84..5574c2c6 100644 --- a/java_runtime/src/classes/java/lang/thread.rs +++ b/java_runtime/src/classes/java/lang/thread.rs @@ -3,7 +3,7 @@ use core::time::Duration; use java_class_proto::{JavaFieldProto, JavaMethodProto}; use java_constants::{FieldAccessFlags, MethodAccessFlags}; -use jvm::{ClassInstanceRef, Jvm, Result, runtime::JavaLangString}; +use jvm::{ClassInstanceRef, GlobalRef, Jvm, Result, runtime::JavaLangString}; use crate::{ RuntimeClassProto, RuntimeContext, SpawnCallback, @@ -191,7 +191,7 @@ impl Thread { struct ThreadStartProxy { jvm: Jvm, thread_id: i32, - this: ClassInstanceRef, + this: GlobalRef, } #[async_trait::async_trait] @@ -226,7 +226,7 @@ impl Thread { } } - let mut this = self.this.clone(); + let mut this = (*self.this).clone(); let cleanup = if let Err(error) = self.jvm.monitor_enter(&self.this).await { Err(error) } else { @@ -253,12 +253,16 @@ impl Thread { let id: i32 = jvm.invoke_virtual(&this, "hashCode", "()I", ()).await?; + let this = match jvm.new_global_ref(&this) { + Some(this) => this, + None => return Err(jvm.exception("java/lang/NullPointerException", "thread is null").await), + }; context.spawn( jvm, Box::new(ThreadStartProxy { jvm: jvm.clone(), thread_id: id, - this: this.clone(), + this, }), ); diff --git a/jvm/src/garbage_collector.rs b/jvm/src/garbage_collector.rs index 7cc76493..580c0130 100644 --- a/jvm/src/garbage_collector.rs +++ b/jvm/src/garbage_collector.rs @@ -8,6 +8,7 @@ use crate::{ClassDefinition, ClassInstance, Field, JavaValue, Jvm, class_loader: pub fn determine_garbage( jvm: &Jvm, threads: &BTreeMap, + global_references: &BTreeMap>, all_class_instances: &HashSet>, classes: &BTreeMap, interned_strings: &[Box], @@ -30,6 +31,10 @@ pub fn determine_garbage( find_reachable_objects(jvm, x, &mut reachable_objects); }); + global_references.values().for_each(|object| { + find_reachable_objects(jvm, object, &mut reachable_objects); + }); + interned_strings.iter().for_each(|x| { find_reachable_objects(jvm, x, &mut reachable_objects); }); diff --git a/jvm/src/global_ref.rs b/jvm/src/global_ref.rs new file mode 100644 index 00000000..87567799 --- /dev/null +++ b/jvm/src/global_ref.rs @@ -0,0 +1,31 @@ +use alloc::{boxed::Box, collections::BTreeMap, sync::Arc}; +use core::{ops::Deref, sync::atomic::AtomicU64}; + +use parking_lot::RwLock; + +use crate::{ClassInstance, ClassInstanceRef}; + +pub(crate) struct GlobalReferences { + pub(crate) next_id: AtomicU64, + pub(crate) objects: RwLock>>, +} + +pub struct GlobalRef { + pub(crate) references: Arc, + pub(crate) id: u64, + pub(crate) reference: ClassInstanceRef, +} + +impl Deref for GlobalRef { + type Target = ClassInstanceRef; + + fn deref(&self) -> &Self::Target { + &self.reference + } +} + +impl Drop for GlobalRef { + fn drop(&mut self) { + self.references.objects.write().remove(&self.id); + } +} diff --git a/jvm/src/jvm.rs b/jvm/src/jvm.rs index bb44ea66..9e282883 100644 --- a/jvm/src/jvm.rs +++ b/jvm/src/jvm.rs @@ -4,7 +4,7 @@ use alloc::{borrow::ToOwned, boxed::Box, collections::BTreeMap, format, string:: use core::{ fmt::Debug, iter, - sync::atomic::{AtomicBool, Ordering}, + sync::atomic::{AtomicBool, AtomicU64, Ordering}, }; use dyn_clone::clone_box; @@ -17,13 +17,14 @@ use crate::{ Result, array_class_instance::{ArrayRawBuffer, ArrayRawBufferMut}, class_definition::ClassDefinition, - class_instance::ClassInstance, + class_instance::{ClassInstance, ClassInstanceRef}, class_loader::{ BootstrapClassLoader, BootstrapClassLoaderWrapper, Class, ClassLoaderWrapper, InitState, InitializationAction, JavaClassLoaderWrapper, }, error::JavaError, field::Field, garbage_collector::determine_garbage, + global_ref::{GlobalRef, GlobalReferences}, invoke_arg::InvokeArg, method::Method, monitor::{Monitor, MonitorWait, MonitorWaitTimeout}, @@ -36,6 +37,7 @@ use crate::{ struct JvmInner { classes: RwLock>, threads: RwLock>, + global_references: Arc, all_objects: RwLock>>, string_pool: RwLock, Box>>, monitors: RwLock>>, @@ -59,6 +61,10 @@ impl Jvm { inner: Arc::new(JvmInner { classes: RwLock::new(BTreeMap::new()), threads: RwLock::new(BTreeMap::new()), + global_references: Arc::new(GlobalReferences { + next_id: AtomicU64::new(0), + objects: RwLock::new(BTreeMap::new()), + }), all_objects: RwLock::new(HashSet::new()), string_pool: RwLock::new(BTreeMap::new()), monitors: RwLock::new(BTreeMap::new()), @@ -107,6 +113,16 @@ impl Jvm { jvm.inner.bootstrapping.store(false, Ordering::Relaxed); + let thread_id = (jvm.inner.get_current_thread_id)(); + jvm.inner + .threads + .write() + .get_mut(&thread_id) + .unwrap() + .top_frame_mut() + .local_variables_mut() + .clear(); + Ok(jvm) } @@ -188,7 +204,19 @@ impl Jvm { self.ensure_initialized(&declaring_class).await?; - Ok(declaring_class.definition.get_static_field(&*field)?.into()) + let value = declaring_class.definition.get_static_field(&*field)?; + if let JavaValue::Object(Some(instance)) = &value { + let thread_id = (self.inner.get_current_thread_id)(); + self.inner + .threads + .write() + .get_mut(&thread_id) + .unwrap() + .top_frame_mut() + .local_variables_mut() + .push(instance.clone()); + } + Ok(value.into()) } else { Err(self .exception("java/lang/NoSuchFieldError", &format!("{class_name}.{name}:{descriptor}")) @@ -230,7 +258,19 @@ impl Jvm { let field = self.find_field(&*instance.class_definition(), name, descriptor)?; if let Some(field) = field { - Ok(instance.get_field(&*field)?.into()) + let value = instance.get_field(&*field)?; + if let JavaValue::Object(Some(instance)) = &value { + let thread_id = (self.inner.get_current_thread_id)(); + self.inner + .threads + .write() + .get_mut(&thread_id) + .unwrap() + .top_frame_mut() + .local_variables_mut() + .push(instance.clone()); + } + Ok(value.into()) } else { Err(self .exception( @@ -407,6 +447,15 @@ impl Jvm { if let Some(array) = array { let values = array.load(offset, count)?; + let thread_id = (self.inner.get_current_thread_id)(); + let mut threads = self.inner.threads.write(); + let local_variables = threads.get_mut(&thread_id).unwrap().top_frame_mut().local_variables_mut(); + values.iter().for_each(|value| { + if let JavaValue::Object(Some(instance)) = value { + local_variables.push(instance.clone()); + } + }); + Ok(iter::IntoIterator::into_iter(values).map(|x| x.into()).collect::>()) } else { Err(self.exception("java/lang/IllegalArgumentException", "Not an array").await) @@ -761,11 +810,12 @@ impl Jvm { let garbage = { let threads = self.inner.threads.read(); + let global_references = self.inner.global_references.objects.read(); let all_objects = self.inner.all_objects.read(); let classes = self.inner.classes.read(); let interned_strings = self.interned_strings(); - determine_garbage(self, &threads, &all_objects, &classes, &interned_strings) + determine_garbage(self, &threads, &global_references, &all_objects, &classes, &interned_strings) }; let garbage_count = garbage.len(); @@ -888,6 +938,18 @@ impl Jvm { Ok(()) } + pub fn new_global_ref(&self, reference: &ClassInstanceRef) -> Option> { + let instance = reference.instance.as_ref()?.clone(); + let id = self.inner.global_references.next_id.fetch_add(1, Ordering::Relaxed); + self.inner.global_references.objects.write().insert(id, instance); + + Some(GlobalRef { + references: self.inner.global_references.clone(), + id, + reference: reference.clone(), + }) + } + pub fn detach_thread(&self) -> Result<()> { let thread_id = (self.inner.get_current_thread_id)(); self.inner.threads.write().remove(&thread_id); @@ -1043,13 +1105,25 @@ impl Jvm { .write() .get_mut(&thread_id) .unwrap() - .push_java_frame(class, class_instance, &method_str); + .push_java_frame(class, class_instance, &method_str, &args); let result = method.run(self, args).await; tracing::trace!("Execute result: {result:?}"); - self.inner.threads.write().get_mut(&thread_id).unwrap().pop_frame(); + let returned_reference = match &result { + Ok(JavaValue::Object(Some(instance))) => Some(instance.clone()), + Err(JavaError::JavaException(exception)) => Some(exception.clone()), + _ => None, + }; + { + let mut threads = self.inner.threads.write(); + let thread = threads.get_mut(&thread_id).unwrap(); + thread.pop_frame(); + if let Some(returned_reference) = returned_reference { + thread.top_frame_mut().local_variables_mut().push(returned_reference); + } + } if let Some(object) = &synchronized_object && let Err(error) = self.monitor_exit(object).await diff --git a/jvm/src/lib.rs b/jvm/src/lib.rs index 9706e426..194fb960 100644 --- a/jvm/src/lib.rs +++ b/jvm/src/lib.rs @@ -10,6 +10,7 @@ mod class_loader; mod error; mod field; mod garbage_collector; +mod global_ref; mod invoke_arg; mod jvm; mod method; @@ -38,6 +39,7 @@ pub use self::{ class_loader::BootstrapClassLoader, error::JavaError, field::Field, + global_ref::GlobalRef, jvm::Jvm, method::Method, monitor::{MonitorWait, MonitorWaitTimeout}, diff --git a/jvm/src/thread.rs b/jvm/src/thread.rs index 77e76d53..b778dbf6 100644 --- a/jvm/src/thread.rs +++ b/jvm/src/thread.rs @@ -4,7 +4,7 @@ use alloc::{ vec::Vec, }; -use crate::{ClassInstance, class_loader::Class}; +use crate::{ClassInstance, JavaValue, class_loader::Class}; pub enum StackFrame { Java(JavaStackFrame), @@ -49,12 +49,18 @@ impl JvmThread { self.java_thread = Some(java_thread); } - pub fn push_java_frame(&mut self, class: &Class, class_instance: Option>, method: &str) { + pub fn push_java_frame(&mut self, class: &Class, class_instance: Option>, method: &str, args: &[JavaValue]) { self.stack.push(StackFrame::Java(JavaStackFrame { class: class.clone(), class_instance, method: method.to_string(), - local_variables: Vec::new(), + local_variables: args + .iter() + .filter_map(|arg| match arg { + JavaValue::Object(Some(instance)) => Some(instance.clone()), + _ => None, + }) + .collect(), })); } diff --git a/jvm/tests/test_garbage_collection.rs b/jvm/tests/test_garbage_collection.rs index 4b1737f1..6568e9b5 100644 --- a/jvm/tests/test_garbage_collection.rs +++ b/jvm/tests/test_garbage_collection.rs @@ -1,6 +1,8 @@ -use jvm::{JavaValue, Result as JvmResult, runtime::JavaLangString}; +use jvm::{Array, ClassInstanceRef, JavaValue, Result as JvmResult, runtime::JavaLangString}; -use test_utils::test_jvm; +use std::collections::BTreeMap; + +use test_utils::{TestRuntime, create_test_jvm, test_jvm}; #[tokio::test] async fn test_garbage_collection() -> JvmResult<()> { @@ -110,3 +112,191 @@ async fn test_garbage_collection_hashtable() -> JvmResult<()> { Ok(()) } + +#[tokio::test] +async fn global_references_are_independent_garbage_collection_roots() -> JvmResult<()> { + let jvm = test_jvm().await?; + + jvm.collect_garbage()?; + + struct Object; + + jvm.push_native_frame(); + let object: ClassInstanceRef = jvm.new_class("java/lang/Object", "()V", ()).await?.into(); + let first = jvm.new_global_ref(&object).unwrap(); + let second = jvm.new_global_ref(&object).unwrap(); + jvm.pop_frame(); + + assert_eq!(jvm.collect_garbage()?, 0); + drop(first); + assert_eq!(jvm.collect_garbage()?, 0); + drop(second); + assert_eq!(jvm.collect_garbage()?, 1); + + let null: ClassInstanceRef = None.into(); + assert!(jvm.new_global_ref(&null).is_none()); + + Ok(()) +} + +#[tokio::test] +async fn array_load_result_is_a_local_reference() -> JvmResult<()> { + let jvm = test_jvm().await?; + + struct Object; + + jvm.push_native_frame(); + let _: ClassInstanceRef> = jvm.instantiate_array("Ljava/lang/Object;", 0).await?.into(); + jvm.pop_frame(); + jvm.collect_garbage()?; + + jvm.push_native_frame(); + let mut array: ClassInstanceRef> = jvm.instantiate_array("Ljava/lang/Object;", 1).await?.into(); + let object: ClassInstanceRef = jvm.new_class("java/lang/Object", "()V", ()).await?.into(); + jvm.store_array(&mut array, 0, [object]).await?; + let array = jvm.new_global_ref(&array).unwrap(); + jvm.pop_frame(); + + assert_eq!(jvm.collect_garbage()?, 0); + + jvm.push_native_frame(); + let _: Vec> = jvm.load_array(&array, 0, 1).await?; + let mut mutable_array = (*array).clone(); + jvm.store_array(&mut mutable_array, 0, [ClassInstanceRef::::new(None)]).await?; + assert_eq!(jvm.collect_garbage()?, 0); + jvm.pop_frame(); + + assert_eq!(jvm.collect_garbage()?, 1); + drop(array); + assert_eq!(jvm.collect_garbage()?, 1); + + Ok(()) +} + +#[tokio::test] +async fn field_and_method_results_are_local_references() -> JvmResult<()> { + let jvm = test_jvm().await?; + + struct Object; + struct Vector; + + jvm.push_native_frame(); + let _: ClassInstanceRef = jvm.new_class("java/util/Vector", "()V", ()).await?.into(); + jvm.pop_frame(); + jvm.collect_garbage()?; + + jvm.push_native_frame(); + let vector: ClassInstanceRef = jvm.new_class("java/util/Vector", "(I)V", (1,)).await?.into(); + let object: ClassInstanceRef = jvm.new_class("java/lang/Object", "()V", ()).await?.into(); + let _: () = jvm.invoke_virtual(&vector, "addElement", "(Ljava/lang/Object;)V", (object,)).await?; + let vector = jvm.new_global_ref(&vector).unwrap(); + jvm.pop_frame(); + + assert_eq!(jvm.collect_garbage()?, 0); + + jvm.push_native_frame(); + let _: ClassInstanceRef> = jvm.get_field(&vector, "elementData", "[Ljava/lang/Object;").await?; + let mut mutable_vector = (*vector).clone(); + jvm.put_field( + &mut mutable_vector, + "elementData", + "[Ljava/lang/Object;", + ClassInstanceRef::>::new(None), + ) + .await?; + assert_eq!(jvm.collect_garbage()?, 0); + jvm.pop_frame(); + + assert_eq!(jvm.collect_garbage()?, 2); + drop(vector); + assert_eq!(jvm.collect_garbage()?, 1); + + jvm.push_native_frame(); + let vector: ClassInstanceRef = jvm.new_class("java/util/Vector", "(I)V", (1,)).await?.into(); + let object: ClassInstanceRef = jvm.new_class("java/lang/Object", "()V", ()).await?.into(); + let _: () = jvm.invoke_virtual(&vector, "addElement", "(Ljava/lang/Object;)V", (object,)).await?; + let vector = jvm.new_global_ref(&vector).unwrap(); + jvm.pop_frame(); + + jvm.push_native_frame(); + let _: ClassInstanceRef = jvm.invoke_virtual(&vector, "remove", "(I)Ljava/lang/Object;", (0,)).await?; + assert_eq!(jvm.collect_garbage()?, 0); + jvm.pop_frame(); + + assert_eq!(jvm.collect_garbage()?, 1); + drop(vector); + assert_eq!(jvm.collect_garbage()?, 2); + + Ok(()) +} + +#[tokio::test] +async fn static_field_result_is_a_local_reference() -> JvmResult<()> { + let jvm = test_jvm().await?; + + struct Object; + + jvm.collect_garbage()?; + + jvm.push_native_frame(); + let _: ClassInstanceRef = jvm.get_static_field("java/lang/System", "out", "Ljava/io/PrintStream;").await?; + jvm.put_static_field("java/lang/System", "out", "Ljava/io/PrintStream;", ClassInstanceRef::::new(None)) + .await?; + assert_eq!(jvm.collect_garbage()?, 0); + jvm.pop_frame(); + + assert!(jvm.collect_garbage()? > 0); + + Ok(()) +} + +#[tokio::test] +async fn returned_exception_is_a_local_reference() -> JvmResult<()> { + let jvm = test_jvm().await?; + + struct Vector; + + jvm.push_native_frame(); + let vector: ClassInstanceRef = jvm.new_class("java/util/Vector", "()V", ()).await?.into(); + let _: jvm::JavaError = jvm + .invoke_virtual::<_, ClassInstanceRef<()>>(&vector, "elementAt", "(I)Ljava/lang/Object;", (0,)) + .await + .unwrap_err(); + jvm.pop_frame(); + jvm.collect_garbage()?; + + jvm.push_native_frame(); + let vector: ClassInstanceRef = jvm.new_class("java/util/Vector", "()V", ()).await?.into(); + let _: jvm::JavaError = jvm + .invoke_virtual::<_, ClassInstanceRef<()>>(&vector, "elementAt", "(I)Ljava/lang/Object;", (0,)) + .await + .unwrap_err(); + + assert_eq!(jvm.collect_garbage()?, 2); + assert_eq!(jvm.collect_garbage()?, 0); + jvm.pop_frame(); + assert_eq!(jvm.collect_garbage()?, 8); + + Ok(()) +} + +#[tokio::test] +async fn thread_start_keeps_the_thread_alive_until_spawn_callback_runs() -> JvmResult<()> { + let runtime = TestRuntime::new_with_queued_spawns(BTreeMap::new()); + let jvm = create_test_jvm(runtime.clone()).await?; + + jvm.collect_garbage()?; + + jvm.push_native_frame(); + let thread = jvm.new_class("java/lang/Thread", "()V", ()).await?; + let _: () = jvm.invoke_virtual(&thread, "start", "()V", ()).await?; + jvm.pop_frame(); + + assert_eq!(jvm.collect_garbage()?, 1); + assert_eq!(jvm.collect_garbage()?, 0); + + drop(runtime.take_spawn_callback().unwrap()); + assert_eq!(jvm.collect_garbage()?, 3); + + Ok(()) +} diff --git a/test_utils/src/lib.rs b/test_utils/src/lib.rs index fd53c499..b4a087b5 100644 --- a/test_utils/src/lib.rs +++ b/test_utils/src/lib.rs @@ -19,11 +19,14 @@ use java_runtime::{ get_runtime_class_proto, }; +type SpawnCallbacks = Arc>>>; + pub struct TestRuntime { filesystem: BTreeMap>, file_table: Arc>>>, next_fd: Arc, exit_status: Arc, + spawn_callbacks: Option, } impl Clone for TestRuntime { @@ -33,6 +36,7 @@ impl Clone for TestRuntime { file_table: self.file_table.clone(), next_fd: self.next_fd.clone(), exit_status: self.exit_status.clone(), + spawn_callbacks: self.spawn_callbacks.clone(), } } } @@ -44,9 +48,24 @@ impl TestRuntime { file_table: Arc::new(Mutex::new(BTreeMap::new())), next_fd: Arc::new(AtomicU32::new(1)), exit_status: Arc::new(AtomicI64::new(i64::MIN)), + spawn_callbacks: None, } } + pub fn new_with_queued_spawns(filesystem: BTreeMap>) -> Self { + Self { + filesystem, + file_table: Arc::new(Mutex::new(BTreeMap::new())), + next_fd: Arc::new(AtomicU32::new(1)), + exit_status: Arc::new(AtomicI64::new(i64::MIN)), + spawn_callbacks: Some(Arc::new(Mutex::new(Vec::new()))), + } + } + + pub fn take_spawn_callback(&self) -> Option> { + self.spawn_callbacks.as_ref()?.lock().unwrap().pop() + } + pub fn exit_status(&self) -> Option { let status = self.exit_status.load(Ordering::SeqCst); (status != i64::MIN).then_some(status as i32) @@ -76,6 +95,11 @@ impl Runtime for TestRuntime { } fn spawn(&self, _jvm: &Jvm, callback: Box) { + if let Some(spawn_callbacks) = &self.spawn_callbacks { + spawn_callbacks.lock().unwrap().push(callback); + return; + } + let task_id = LAST_TASK_ID.fetch_add(1, Ordering::SeqCst); tokio::spawn(async move { TASK_ID