|
| 1 | +use alloc::{boxed::Box, collections::BTreeMap, sync::Arc, vec::Vec}; |
| 2 | +use core::mem::forget; |
| 3 | + |
| 4 | +use bytemuck::cast_slice; |
| 5 | +use hashbrown::{hash_set::Entry, HashMap, HashSet}; |
| 6 | +use parking_lot::Mutex; |
| 7 | + |
| 8 | +use crate::{thread::JvmThread, ClassInstance, JavaType, JavaValue, Jvm}; |
| 9 | + |
| 10 | +// XXX java/util/Vector, java/util/HashMap internal.. |
| 11 | +type RustVector = Arc<Mutex<Vec<Box<dyn ClassInstance>>>>; |
| 12 | +type RustHashMap = Arc<Mutex<HashMap<i32, Vec<(Box<dyn ClassInstance>, Box<dyn ClassInstance>)>>>>; |
| 13 | + |
| 14 | +pub fn determine_garbage( |
| 15 | + jvm: &Jvm, |
| 16 | + threads: &BTreeMap<u64, JvmThread>, |
| 17 | + all_class_instances: &HashSet<Box<dyn ClassInstance>>, |
| 18 | + classes: Vec<Box<dyn ClassInstance>>, |
| 19 | +) -> Vec<Box<dyn ClassInstance>> { |
| 20 | + let mut reachable_objects = classes.into_iter().collect::<HashSet<_>>(); |
| 21 | + |
| 22 | + threads |
| 23 | + .iter() |
| 24 | + .flat_map(|(_, thread)| thread.iter_frame().flat_map(|stack| stack.local_variables())) |
| 25 | + .for_each(|x| { |
| 26 | + find_reachable_objects(jvm, x, &mut reachable_objects); |
| 27 | + }); |
| 28 | + |
| 29 | + // HACK we should test if class loader is in use |
| 30 | + for class_instance in all_class_instances.iter() { |
| 31 | + if jvm.is_instance(&**class_instance, "java/lang/ClassLoader") { |
| 32 | + find_reachable_objects(jvm, class_instance, &mut reachable_objects); |
| 33 | + } |
| 34 | + } |
| 35 | + |
| 36 | + all_class_instances.difference(&reachable_objects).cloned().collect() |
| 37 | +} |
| 38 | + |
| 39 | +#[allow(clippy::borrowed_box)] |
| 40 | +fn find_reachable_objects(jvm: &Jvm, object: &Box<dyn ClassInstance>, reachable_objects: &mut HashSet<Box<dyn ClassInstance>>) { |
| 41 | + let entry = reachable_objects.entry(object.clone()); |
| 42 | + if let Entry::Occupied(_) = entry { |
| 43 | + return; |
| 44 | + } |
| 45 | + entry.insert(); |
| 46 | + |
| 47 | + let fields = object.class_definition().fields(); |
| 48 | + |
| 49 | + for field in fields { |
| 50 | + match field.r#type() { |
| 51 | + JavaType::Class(_) => { |
| 52 | + let value = object.get_field(&*field).unwrap(); |
| 53 | + if let JavaValue::Object(Some(value)) = value { |
| 54 | + find_reachable_objects(jvm, &value, reachable_objects); |
| 55 | + |
| 56 | + // XXX we have to deal with java value wrapped inside rust type e.g. java.util.Vector, java.util.Hashtable |
| 57 | + if jvm.is_instance(&*value, "java/util/Vector") { |
| 58 | + let members = vector_members(&*value); |
| 59 | + assert!(members.len() == 1); |
| 60 | + for member in members { |
| 61 | + find_reachable_objects(jvm, &member, reachable_objects); |
| 62 | + } |
| 63 | + } else if jvm.is_instance(&*value, "java/util/Hashtable") { |
| 64 | + let members = hashtable_members(&*value); |
| 65 | + for member in members { |
| 66 | + find_reachable_objects(jvm, &member, reachable_objects); |
| 67 | + } |
| 68 | + } |
| 69 | + } |
| 70 | + } |
| 71 | + JavaType::Array(x) => { |
| 72 | + if matches!(*x, JavaType::Class(_)) { |
| 73 | + let value = object.get_field(&*field).unwrap(); |
| 74 | + if let JavaValue::Object(Some(value)) = value { |
| 75 | + let array = value.as_array_instance().unwrap(); |
| 76 | + let values = array.load(0, array.length()).unwrap(); |
| 77 | + |
| 78 | + for value in values { |
| 79 | + if let JavaValue::Object(Some(value)) = value { |
| 80 | + find_reachable_objects(jvm, &value, reachable_objects); |
| 81 | + } |
| 82 | + } |
| 83 | + } |
| 84 | + } |
| 85 | + } |
| 86 | + _ => {} |
| 87 | + } |
| 88 | + } |
| 89 | +} |
| 90 | + |
| 91 | +// Same as Jvm's one but without async |
| 92 | +fn get_rust_object_field<T: Clone>(object: &dyn ClassInstance, field_name: &str) -> T { |
| 93 | + let field = object.class_definition().field(field_name, "Ljava/lang/Object;", true).unwrap(); |
| 94 | + let value = object.get_field(&*field).unwrap(); |
| 95 | + let buf: Vec<i8> = match value { |
| 96 | + JavaValue::Object(Some(value)) => { |
| 97 | + let value_array = value.as_array_instance().unwrap(); |
| 98 | + |
| 99 | + value_array.load(0, value_array.length()).unwrap().into_iter().map(|x| x.into()).collect() |
| 100 | + } |
| 101 | + _ => panic!("Invalid field type"), |
| 102 | + }; |
| 103 | + |
| 104 | + let rust_raw = usize::from_le_bytes(cast_slice(&buf).try_into().unwrap()); |
| 105 | + |
| 106 | + let rust = unsafe { Box::from_raw(rust_raw as *mut T) }; |
| 107 | + let result = (*rust).clone(); |
| 108 | + |
| 109 | + forget(rust); // do not drop box as we still have it in java memory |
| 110 | + |
| 111 | + result |
| 112 | +} |
| 113 | + |
| 114 | +fn vector_members(vector: &dyn ClassInstance) -> Vec<Box<dyn ClassInstance>> { |
| 115 | + let rust_vector: RustVector = get_rust_object_field(vector, "raw"); |
| 116 | + |
| 117 | + let rust_vector = rust_vector.lock(); |
| 118 | + rust_vector.iter().cloned().collect() |
| 119 | +} |
| 120 | + |
| 121 | +fn hashtable_members(hashtable: &dyn ClassInstance) -> Vec<Box<dyn ClassInstance>> { |
| 122 | + let rust_hashmap: RustHashMap = get_rust_object_field(hashtable, "raw"); |
| 123 | + |
| 124 | + let rust_hashmap = rust_hashmap.lock(); |
| 125 | + rust_hashmap.iter().flat_map(|(_, v)| v.iter().map(|x| x.1.clone())).collect() |
| 126 | +} |
0 commit comments