From a377e64fe40e41c142c8ad81e303a39e0810b0b3 Mon Sep 17 00:00:00 2001 From: Inseok Lee Date: Sun, 9 Aug 2026 17:10:27 +0900 Subject: [PATCH 1/3] Implement CDC 1.1 LinkedHashMap --- AGENTS.md | 3 + java_runtime/src/classes/java/util.rs | 12 + .../src/classes/java/util/hash_map.rs | 258 +++++++++--- .../src/classes/java/util/hash_map_entry.rs | 23 +- .../classes/java/util/hash_map_entry_set.rs | 6 +- .../src/classes/java/util/hash_map_key_set.rs | 6 +- .../src/classes/java/util/hash_map_values.rs | 12 +- .../src/classes/java/util/linked_hash_map.rs | 315 ++++++++++++++ .../java/util/linked_hash_map_entry.rs | 88 ++++ .../util/linked_hash_map_entry_iterator.rs | 53 +++ .../java/util/linked_hash_map_key_iterator.rs | 53 +++ .../linked_hash_map_linked_hash_iterator.rs | 106 +++++ .../util/linked_hash_map_value_iterator.rs | 53 +++ java_runtime/src/loader.rs | 6 + java_runtime/tests/classes/java/util/mod.rs | 1 + .../classes/java/util/test_linked_hash_map.rs | 387 ++++++++++++++++++ 16 files changed, 1309 insertions(+), 73 deletions(-) create mode 100644 java_runtime/src/classes/java/util/linked_hash_map.rs create mode 100644 java_runtime/src/classes/java/util/linked_hash_map_entry.rs create mode 100644 java_runtime/src/classes/java/util/linked_hash_map_entry_iterator.rs create mode 100644 java_runtime/src/classes/java/util/linked_hash_map_key_iterator.rs create mode 100644 java_runtime/src/classes/java/util/linked_hash_map_linked_hash_iterator.rs create mode 100644 java_runtime/src/classes/java/util/linked_hash_map_value_iterator.rs create mode 100644 java_runtime/tests/classes/java/util/test_linked_hash_map.rs diff --git a/AGENTS.md b/AGENTS.md index 409e9c0b..896cc966 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -27,3 +27,6 @@ - Keep `java_runtime/tests/classes` limited to Java standard library class and API behavior. - Test JVM and interpreter semantics, including class initialization, bytecode execution, and monitor behavior, with compiled Java fixtures under `test_data/src` and expected output under `test_data`, executed by `tests/test_class.rs`. - Do not place JVM core behavior tests in the `java_runtime` standard library test tree. + +## Compatibility Sources +- Implement Java compatibility from public specifications, Javadocs, and observable behavior tests. Do not consult or reproduce OpenJDK or other Java runtime implementation source code; keep the implementation independent to avoid licensing and provenance concerns. diff --git a/java_runtime/src/classes/java/util.rs b/java_runtime/src/classes/java/util.rs index 0adaf164..7ccf408d 100644 --- a/java_runtime/src/classes/java/util.rs +++ b/java_runtime/src/classes/java/util.rs @@ -66,6 +66,12 @@ mod illegal_format_flags_exception; mod illegal_format_precision_exception; mod illegal_format_width_exception; mod iterator; +mod linked_hash_map; +mod linked_hash_map_entry; +mod linked_hash_map_entry_iterator; +mod linked_hash_map_key_iterator; +mod linked_hash_map_linked_hash_iterator; +mod linked_hash_map_value_iterator; mod linked_list; mod linked_list_entry; mod linked_list_itr; @@ -171,6 +177,12 @@ pub use self::{ illegal_format_precision_exception::IllegalFormatPrecisionException, illegal_format_width_exception::IllegalFormatWidthException, iterator::Iterator, + linked_hash_map::LinkedHashMap, + linked_hash_map_entry::LinkedHashMapEntry, + linked_hash_map_entry_iterator::LinkedHashMapEntryIterator, + linked_hash_map_key_iterator::LinkedHashMapKeyIterator, + linked_hash_map_linked_hash_iterator::LinkedHashMapLinkedHashIterator, + linked_hash_map_value_iterator::LinkedHashMapValueIterator, linked_list::LinkedList, linked_list_entry::LinkedListEntry, linked_list_itr::LinkedListItr, diff --git a/java_runtime/src/classes/java/util/hash_map.rs b/java_runtime/src/classes/java/util/hash_map.rs index 8038da37..e43d0271 100644 --- a/java_runtime/src/classes/java/util/hash_map.rs +++ b/java_runtime/src/classes/java/util/hash_map.rs @@ -21,31 +21,55 @@ impl HashMap { parent_class: Some("java/util/AbstractMap"), interfaces: vec![], methods: vec![ - JavaMethodProto::new("", "()V", Self::init, Default::default()), - JavaMethodProto::new("", "(I)V", Self::init_with_capacity, Default::default()), + JavaMethodProto::new("", "()V", Self::init, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("", "(I)V", Self::init_with_capacity, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("", "(IF)V", Self::init_with_capacity_and_load_factor, MethodAccessFlags::PUBLIC), JavaMethodProto::new("", "(Ljava/util/Map;)V", Self::init_from_map, MethodAccessFlags::PUBLIC), - JavaMethodProto::new("size", "()I", Self::size, Default::default()), - JavaMethodProto::new("isEmpty", "()Z", Self::is_empty, Default::default()), - JavaMethodProto::new("containsKey", "(Ljava/lang/Object;)Z", Self::contains_key, Default::default()), - JavaMethodProto::new("containsValue", "(Ljava/lang/Object;)Z", Self::contains_value, Default::default()), - JavaMethodProto::new("get", "(Ljava/lang/Object;)Ljava/lang/Object;", Self::get, Default::default()), + JavaMethodProto::new("size", "()I", Self::size, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("isEmpty", "()Z", Self::is_empty, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("containsKey", "(Ljava/lang/Object;)Z", Self::contains_key, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("containsValue", "(Ljava/lang/Object;)Z", Self::contains_value, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("get", "(Ljava/lang/Object;)Ljava/lang/Object;", Self::get, MethodAccessFlags::PUBLIC), JavaMethodProto::new( "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", Self::put, - Default::default(), + MethodAccessFlags::PUBLIC, ), JavaMethodProto::new("putAll", "(Ljava/util/Map;)V", Self::put_all, MethodAccessFlags::PUBLIC), - JavaMethodProto::new("remove", "(Ljava/lang/Object;)Ljava/lang/Object;", Self::remove, Default::default()), - JavaMethodProto::new("clear", "()V", Self::clear, Default::default()), - JavaMethodProto::new("keySet", "()Ljava/util/Set;", Self::key_set, Default::default()), - JavaMethodProto::new("values", "()Ljava/util/Collection;", Self::values, Default::default()), - JavaMethodProto::new("entrySet", "()Ljava/util/Set;", Self::entry_set, Default::default()), + JavaMethodProto::new( + "remove", + "(Ljava/lang/Object;)Ljava/lang/Object;", + Self::remove, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new("clear", "()V", Self::clear, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("keySet", "()Ljava/util/Set;", Self::key_set, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("values", "()Ljava/util/Collection;", Self::values, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("entrySet", "()Ljava/util/Set;", Self::entry_set, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("initializeMap", "()V", Self::initialize_map, Default::default()), + JavaMethodProto::new( + "storeNewEntry", + "(ILjava/lang/Object;Ljava/lang/Object;I)V", + Self::store_new_entry, + Default::default(), + ), + JavaMethodProto::new( + "insertNewEntry", + "(ILjava/lang/Object;Ljava/lang/Object;I)V", + Self::insert_new_entry, + Default::default(), + ), + JavaMethodProto::new("keyIterator", "()Ljava/util/Iterator;", Self::key_iterator, Default::default()), + JavaMethodProto::new("valueIterator", "()Ljava/util/Iterator;", Self::value_iterator, Default::default()), + JavaMethodProto::new("entryIterator", "()Ljava/util/Iterator;", Self::entry_iterator, Default::default()), ], fields: vec![ JavaFieldProto::new("table", "[Ljava/util/HashMap$Entry;", Default::default()), JavaFieldProto::new("size", "I", Default::default()), JavaFieldProto::new("threshold", "I", Default::default()), + JavaFieldProto::new("loadFactor", "F", Default::default()), + JavaFieldProto::new("modCount", "I", Default::default()), ], access_flags: ClassAccessFlags::PUBLIC, } @@ -55,7 +79,13 @@ impl HashMap { tracing::debug!("java.util.HashMap::({this:?})"); let _: () = jvm - .invoke_special(&this, "java/util/HashMap", "", "(I)V", (DEFAULT_INITIAL_CAPACITY,)) + .invoke_special( + &this, + "java/util/HashMap", + "", + "(IF)V", + (DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR), + ) .await?; Ok(()) @@ -85,38 +115,60 @@ impl HashMap { Ok(entry_set.into()) } - pub(super) async fn keys_snapshot(jvm: &Jvm, this: &ClassInstanceRef) -> Result>> { + async fn keys_snapshot(jvm: &Jvm, this: &ClassInstanceRef) -> Result>> { Self::snapshot_entries(jvm, this, SnapshotKind::Keys).await } - pub(super) async fn values_snapshot(jvm: &Jvm, this: &ClassInstanceRef) -> Result>> { + async fn values_snapshot(jvm: &Jvm, this: &ClassInstanceRef) -> Result>> { Self::snapshot_entries(jvm, this, SnapshotKind::Values).await } - pub(super) async fn entries_snapshot(jvm: &Jvm, this: &ClassInstanceRef) -> Result>> { + async fn entries_snapshot(jvm: &Jvm, this: &ClassInstanceRef) -> Result>> { Self::snapshot_entries(jvm, this, SnapshotKind::Entries).await } - async fn init_with_capacity(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, capacity: i32) -> Result<()> { + async fn init_with_capacity(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, capacity: i32) -> Result<()> { tracing::debug!("java.util.HashMap::({this:?}, {capacity:?})"); + jvm.invoke_special(&this, "java/util/HashMap", "", "(IF)V", (capacity, DEFAULT_LOAD_FACTOR)) + .await + } + + async fn init_with_capacity_and_load_factor( + jvm: &Jvm, + _: &mut RuntimeContext, + mut this: ClassInstanceRef, + capacity: i32, + load_factor: f32, + ) -> Result<()> { + tracing::debug!("java.util.HashMap::({this:?}, {capacity:?}, {load_factor:?})"); + if capacity < 0 { return Err(jvm .exception("java/lang/IllegalArgumentException", &format!("Illegal Capacity: {capacity}")) .await); } + if load_factor <= 0.0 || load_factor.is_nan() { + return Err(jvm + .exception("java/lang/IllegalArgumentException", &format!("Illegal Load: {load_factor}")) + .await); + } let _: () = jvm.invoke_special(&this, "java/util/AbstractMap", "", "()V", ()).await?; let table: ClassInstanceRef> = jvm.instantiate_array("Ljava/util/HashMap$Entry;", capacity as usize).await?.into(); jvm.put_field(&mut this, "table", "[Ljava/util/HashMap$Entry;", table).await?; jvm.put_field(&mut this, "size", "I", 0).await?; - jvm.put_field(&mut this, "threshold", "I", Self::threshold_for_capacity(capacity)).await?; + jvm.put_field(&mut this, "threshold", "I", Self::threshold_for_capacity(capacity, load_factor)) + .await?; + jvm.put_field(&mut this, "loadFactor", "F", load_factor).await?; + jvm.put_field(&mut this, "modCount", "I", 0).await?; + let _: () = jvm.invoke_virtual(&this, "initializeMap", "()V", ()).await?; Ok(()) } - async fn init_from_map(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, map: ClassInstanceRef) -> Result<()> { + async fn init_from_map(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, map: ClassInstanceRef) -> Result<()> { tracing::debug!("java.util.HashMap::({this:?}, {map:?})"); if map.is_null() { @@ -124,8 +176,17 @@ impl HashMap { } let size: i32 = jvm.invoke_virtual(&map, "size", "()I", ()).await?; let capacity = size.saturating_mul(2).max(DEFAULT_INITIAL_CAPACITY); - let _: () = jvm.invoke_special(&this, "java/util/HashMap", "", "(I)V", (capacity,)).await?; - let _: () = jvm.invoke_virtual(&this, "putAll", "(Ljava/util/Map;)V", (map,)).await?; + let _: () = jvm + .invoke_special(&this, "java/util/HashMap", "", "(IF)V", (capacity, DEFAULT_LOAD_FACTOR)) + .await?; + let entry_set: ClassInstanceRef = jvm.invoke_virtual(&map, "entrySet", "()Ljava/util/Set;", ()).await?; + let entries: ClassInstanceRef> = jvm.invoke_virtual(&entry_set, "toArray", "()[Ljava/lang/Object;", ()).await?; + let count = jvm.array_length(&entries).await?; + for entry in jvm.load_array::>(&entries, 0, count).await? { + let key: ClassInstanceRef = jvm.invoke_virtual(&entry, "getKey", "()Ljava/lang/Object;", ()).await?; + let value: ClassInstanceRef = jvm.invoke_virtual(&entry, "getValue", "()Ljava/lang/Object;", ()).await?; + Self::put_for_create(jvm, &mut this, key, value).await?; + } Ok(()) } @@ -195,7 +256,7 @@ impl HashMap { let key_hash = Self::object_hash_or_zero(jvm, &key).await?; Self::ensure_table_for_insert(jvm, &mut this).await?; - let mut table: ClassInstanceRef> = jvm.get_field(&this, "table", "[Ljava/util/HashMap$Entry;").await?; + let table: ClassInstanceRef> = jvm.get_field(&this, "table", "[Ljava/util/HashMap$Entry;").await?; let table_len = jvm.array_length(&table).await?; let bucket_index = Self::bucket_index(key_hash, table_len); @@ -207,6 +268,7 @@ impl HashMap { if Self::keys_equal(jvm, &key, &entry_key).await? { let old_value: ClassInstanceRef = jvm.get_field(&entry, "value", "Ljava/lang/Object;").await?; jvm.put_field(&mut entry, "value", "Ljava/lang/Object;", value).await?; + let _: () = jvm.invoke_virtual(&entry, "onAccess", "(Ljava/util/HashMap;)V", (this.clone(),)).await?; return Ok(old_value); } } @@ -214,27 +276,14 @@ impl HashMap { entry = jvm.get_field(&entry, "next", "Ljava/util/HashMap$Entry;").await?; } - let size: i32 = jvm.get_field(&this, "size", "I").await?; - let threshold: i32 = jvm.get_field(&this, "threshold", "I").await?; - let bucket_index = if size >= threshold { - Self::rehash(jvm, &mut this).await?; - table = jvm.get_field(&this, "table", "[Ljava/util/HashMap$Entry;").await?; - Self::bucket_index(key_hash, jvm.array_length(&table).await?) - } else { - bucket_index - }; - - let existing = Self::load_bucket(jvm, &table, bucket_index).await?; - let new_entry: ClassInstanceRef = jvm - .new_class( - "java/util/HashMap$Entry", - "(ILjava/lang/Object;Ljava/lang/Object;Ljava/util/HashMap$Entry;)V", - (key_hash, key, value, existing), + let _: () = jvm + .invoke_virtual( + &this, + "insertNewEntry", + "(ILjava/lang/Object;Ljava/lang/Object;I)V", + (key_hash, key, value, bucket_index as i32), ) - .await? - .into(); - jvm.store_array(&mut table, bucket_index, core::iter::once(new_entry)).await?; - jvm.put_field(&mut this, "size", "I", size + 1).await?; + .await?; Ok(None.into()) } @@ -277,11 +326,14 @@ impl HashMap { } let old_value: ClassInstanceRef = jvm.get_field(&entry, "value", "Ljava/lang/Object;").await?; + let _: () = jvm.invoke_virtual(&entry, "onRemoval", "(Ljava/util/HashMap;)V", (this.clone(),)).await?; let null_entry: ClassInstanceRef = None.into(); jvm.put_field(&mut entry, "next", "Ljava/util/HashMap$Entry;", null_entry).await?; let size: i32 = jvm.get_field(&this, "size", "I").await?; jvm.put_field(&mut this, "size", "I", size - 1).await?; + let mod_count: i32 = jvm.get_field(&this, "modCount", "I").await?; + jvm.put_field(&mut this, "modCount", "I", mod_count.wrapping_add(1)).await?; return Ok(old_value); } @@ -304,10 +356,95 @@ impl HashMap { jvm.store_array(&mut table, 0, nulls).await?; } jvm.put_field(&mut this, "size", "I", 0).await?; + let mod_count: i32 = jvm.get_field(&this, "modCount", "I").await?; + jvm.put_field(&mut this, "modCount", "I", mod_count.wrapping_add(1)).await?; + + Ok(()) + } + async fn initialize_map(_: &Jvm, _: &mut RuntimeContext, _: ClassInstanceRef) -> Result<()> { Ok(()) } + async fn store_new_entry( + jvm: &Jvm, + _: &mut RuntimeContext, + mut this: ClassInstanceRef, + hash: i32, + key: ClassInstanceRef, + value: ClassInstanceRef, + bucket_index: i32, + ) -> Result<()> { + let mut table: ClassInstanceRef> = jvm.get_field(&this, "table", "[Ljava/util/HashMap$Entry;").await?; + let existing = Self::load_bucket(jvm, &table, bucket_index as usize).await?; + let entry: ClassInstanceRef = jvm + .new_class( + "java/util/HashMap$Entry", + "(ILjava/lang/Object;Ljava/lang/Object;Ljava/util/HashMap$Entry;)V", + (hash, key, value, existing), + ) + .await? + .into(); + jvm.store_array(&mut table, bucket_index as usize, core::iter::once(entry)).await?; + let size: i32 = jvm.get_field(&this, "size", "I").await?; + jvm.put_field(&mut this, "size", "I", size + 1).await + } + + async fn insert_new_entry( + jvm: &Jvm, + _: &mut RuntimeContext, + mut this: ClassInstanceRef, + hash: i32, + key: ClassInstanceRef, + value: ClassInstanceRef, + bucket_index: i32, + ) -> Result<()> { + let size: i32 = jvm.get_field(&this, "size", "I").await?; + let mod_count: i32 = jvm.get_field(&this, "modCount", "I").await?; + jvm.put_field(&mut this, "modCount", "I", mod_count.wrapping_add(1)).await?; + let _: () = jvm + .invoke_virtual( + &this, + "storeNewEntry", + "(ILjava/lang/Object;Ljava/lang/Object;I)V", + (hash, key, value, bucket_index), + ) + .await?; + let threshold: i32 = jvm.get_field(&this, "threshold", "I").await?; + if size >= threshold { + Self::rehash(jvm, &mut this).await?; + } + + Ok(()) + } + + async fn key_iterator(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let snapshot = Self::keys_snapshot(jvm, &this).await?; + + Ok(jvm + .new_class("java/util/HashMap$KeyIterator", "([Ljava/lang/Object;)V", (snapshot,)) + .await? + .into()) + } + + async fn value_iterator(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let snapshot = Self::values_snapshot(jvm, &this).await?; + + Ok(jvm + .new_class("java/util/HashMap$ValueIterator", "([Ljava/lang/Object;)V", (snapshot,)) + .await? + .into()) + } + + async fn entry_iterator(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let snapshot = Self::entries_snapshot(jvm, &this).await?; + + Ok(jvm + .new_class("java/util/HashMap$EntryIterator", "([Ljava/lang/Object;)V", (snapshot,)) + .await? + .into()) + } + pub(super) async fn find_entry( jvm: &Jvm, this: &ClassInstanceRef, @@ -337,6 +474,25 @@ impl HashMap { Ok(None.into()) } + pub(super) async fn put_for_create( + jvm: &Jvm, + this: &mut ClassInstanceRef, + key: ClassInstanceRef, + value: ClassInstanceRef, + ) -> Result<()> { + let hash = Self::object_hash_or_zero(jvm, &key).await?; + Self::ensure_table_for_insert(jvm, this).await?; + let table: ClassInstanceRef> = jvm.get_field(this, "table", "[Ljava/util/HashMap$Entry;").await?; + let bucket_index = Self::bucket_index(hash, jvm.array_length(&table).await?); + jvm.invoke_virtual( + this, + "storeNewEntry", + "(ILjava/lang/Object;Ljava/lang/Object;I)V", + (hash, key, value, bucket_index as i32), + ) + .await + } + async fn ensure_table_for_insert(jvm: &Jvm, this: &mut ClassInstanceRef) -> Result<()> { let table: ClassInstanceRef> = jvm.get_field(this, "table", "[Ljava/util/HashMap$Entry;").await?; if jvm.array_length(&table).await? > 0 { @@ -346,13 +502,14 @@ impl HashMap { let new_capacity = 1; let new_table: ClassInstanceRef> = jvm.instantiate_array("Ljava/util/HashMap$Entry;", new_capacity).await?.into(); jvm.put_field(this, "table", "[Ljava/util/HashMap$Entry;", new_table).await?; - jvm.put_field(this, "threshold", "I", Self::threshold_for_capacity(new_capacity as i32)) + let load_factor: f32 = jvm.get_field(this, "loadFactor", "F").await?; + jvm.put_field(this, "threshold", "I", Self::threshold_for_capacity(new_capacity as i32, load_factor)) .await?; Ok(()) } - async fn rehash(jvm: &Jvm, this: &mut ClassInstanceRef) -> Result<()> { + pub(super) async fn rehash(jvm: &Jvm, this: &mut ClassInstanceRef) -> Result<()> { let old_table: ClassInstanceRef> = jvm.get_field(this, "table", "[Ljava/util/HashMap$Entry;").await?; let old_capacity = jvm.array_length(&old_table).await?; let new_capacity = old_capacity * 2 + 1; @@ -374,7 +531,8 @@ impl HashMap { } jvm.put_field(this, "table", "[Ljava/util/HashMap$Entry;", new_table).await?; - jvm.put_field(this, "threshold", "I", Self::threshold_for_capacity(new_capacity as i32)) + let load_factor: f32 = jvm.get_field(this, "loadFactor", "F").await?; + jvm.put_field(this, "threshold", "I", Self::threshold_for_capacity(new_capacity as i32, load_factor)) .await?; Ok(()) @@ -449,12 +607,8 @@ impl HashMap { Ok(snapshot) } - fn threshold_for_capacity(capacity: i32) -> i32 { - if capacity <= 0 { - 0 - } else { - (capacity as f32 * DEFAULT_LOAD_FACTOR) as i32 - } + fn threshold_for_capacity(capacity: i32, load_factor: f32) -> i32 { + if capacity <= 0 { 0 } else { (capacity as f32 * load_factor) as i32 } } } diff --git a/java_runtime/src/classes/java/util/hash_map_entry.rs b/java_runtime/src/classes/java/util/hash_map_entry.rs index 7ca2d350..4ec0e4ea 100644 --- a/java_runtime/src/classes/java/util/hash_map_entry.rs +++ b/java_runtime/src/classes/java/util/hash_map_entry.rs @@ -6,6 +6,8 @@ use jvm::{ClassInstanceRef, Jvm, Result}; use crate::{RuntimeClassProto, RuntimeContext, classes::java::lang::Object}; +use super::HashMap; + // class java.util.HashMap$Entry pub struct HashMapEntry; @@ -22,11 +24,18 @@ impl HashMapEntry { Self::init, Default::default(), ), - JavaMethodProto::new("getKey", "()Ljava/lang/Object;", Self::get_key, Default::default()), - JavaMethodProto::new("getValue", "()Ljava/lang/Object;", Self::get_value, Default::default()), - JavaMethodProto::new("setValue", "(Ljava/lang/Object;)Ljava/lang/Object;", Self::set_value, Default::default()), + JavaMethodProto::new("getKey", "()Ljava/lang/Object;", Self::get_key, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("getValue", "()Ljava/lang/Object;", Self::get_value, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "setValue", + "(Ljava/lang/Object;)Ljava/lang/Object;", + Self::set_value, + MethodAccessFlags::PUBLIC, + ), JavaMethodProto::new("equals", "(Ljava/lang/Object;)Z", Self::equals, MethodAccessFlags::PUBLIC), JavaMethodProto::new("hashCode", "()I", Self::hash_code, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("onAccess", "(Ljava/util/HashMap;)V", Self::on_access, Default::default()), + JavaMethodProto::new("onRemoval", "(Ljava/util/HashMap;)V", Self::on_removal, Default::default()), ], fields: vec![ JavaFieldProto::new("hash", "I", Default::default()), @@ -125,4 +134,12 @@ impl HashMapEntry { }; Ok(key_hash ^ value_hash) } + + async fn on_access(_: &Jvm, _: &mut RuntimeContext, _: ClassInstanceRef, _: ClassInstanceRef) -> Result<()> { + Ok(()) + } + + async fn on_removal(_: &Jvm, _: &mut RuntimeContext, _: ClassInstanceRef, _: ClassInstanceRef) -> Result<()> { + Ok(()) + } } diff --git a/java_runtime/src/classes/java/util/hash_map_entry_set.rs b/java_runtime/src/classes/java/util/hash_map_entry_set.rs index 0ecaf72f..4f51083e 100644 --- a/java_runtime/src/classes/java/util/hash_map_entry_set.rs +++ b/java_runtime/src/classes/java/util/hash_map_entry_set.rs @@ -126,12 +126,8 @@ impl HashMapEntrySet { tracing::debug!("java.util.HashMap$EntrySet::iterator({this:?})"); let map: ClassInstanceRef = jvm.get_field(&this, "map", "Ljava/util/HashMap;").await?; - let snapshot = HashMap::entries_snapshot(jvm, &map).await?; - let iterator = jvm - .new_class("java/util/HashMap$EntryIterator", "([Ljava/lang/Object;)V", (snapshot,)) - .await?; - Ok(iterator.into()) + jvm.invoke_virtual(&map, "entryIterator", "()Ljava/util/Iterator;", ()).await } async fn object_equals(jvm: &Jvm, left: &ClassInstanceRef, right: &ClassInstanceRef) -> Result { diff --git a/java_runtime/src/classes/java/util/hash_map_key_set.rs b/java_runtime/src/classes/java/util/hash_map_key_set.rs index 9a8c9a1a..cc083a2a 100644 --- a/java_runtime/src/classes/java/util/hash_map_key_set.rs +++ b/java_runtime/src/classes/java/util/hash_map_key_set.rs @@ -91,11 +91,7 @@ impl HashMapKeySet { tracing::debug!("java.util.HashMap$KeySet::iterator({this:?})"); let map: ClassInstanceRef = jvm.get_field(&this, "map", "Ljava/util/HashMap;").await?; - let snapshot = HashMap::keys_snapshot(jvm, &map).await?; - let iterator = jvm - .new_class("java/util/HashMap$KeyIterator", "([Ljava/lang/Object;)V", (snapshot,)) - .await?; - Ok(iterator.into()) + jvm.invoke_virtual(&map, "keyIterator", "()Ljava/util/Iterator;", ()).await } } diff --git a/java_runtime/src/classes/java/util/hash_map_values.rs b/java_runtime/src/classes/java/util/hash_map_values.rs index 4dd2fed0..323811d6 100644 --- a/java_runtime/src/classes/java/util/hash_map_values.rs +++ b/java_runtime/src/classes/java/util/hash_map_values.rs @@ -68,9 +68,9 @@ impl HashMapValues { tracing::debug!("java.util.HashMap$Values::remove({this:?}, {value:?})"); let map: ClassInstanceRef = jvm.get_field(&this, "map", "Ljava/util/HashMap;").await?; - let entries = HashMap::entries_snapshot(jvm, &map).await?; - let count = jvm.array_length(&entries).await?; - for entry in jvm.load_array::>(&entries, 0, count).await? { + let iterator: ClassInstanceRef = jvm.invoke_virtual(&map, "entryIterator", "()Ljava/util/Iterator;", ()).await?; + while jvm.invoke_virtual::<_, bool>(&iterator, "hasNext", "()Z", ()).await? { + let entry: ClassInstanceRef = jvm.invoke_virtual(&iterator, "next", "()Ljava/lang/Object;", ()).await?; let entry_value: ClassInstanceRef = jvm.invoke_virtual(&entry, "getValue", "()Ljava/lang/Object;", ()).await?; let equal = if value.is_null() { entry_value.is_null() @@ -103,11 +103,7 @@ impl HashMapValues { tracing::debug!("java.util.HashMap$Values::iterator({this:?})"); let map: ClassInstanceRef = jvm.get_field(&this, "map", "Ljava/util/HashMap;").await?; - let snapshot = HashMap::values_snapshot(jvm, &map).await?; - let iterator = jvm - .new_class("java/util/HashMap$ValueIterator", "([Ljava/lang/Object;)V", (snapshot,)) - .await?; - Ok(iterator.into()) + jvm.invoke_virtual(&map, "valueIterator", "()Ljava/util/Iterator;", ()).await } } diff --git a/java_runtime/src/classes/java/util/linked_hash_map.rs b/java_runtime/src/classes/java/util/linked_hash_map.rs new file mode 100644 index 00000000..92f7b1d3 --- /dev/null +++ b/java_runtime/src/classes/java/util/linked_hash_map.rs @@ -0,0 +1,315 @@ +use alloc::vec; + +use java_class_proto::{JavaFieldProto, JavaMethodProto}; +use java_constants::{ClassAccessFlags, FieldAccessFlags, MethodAccessFlags}; +use jvm::{Array, ClassInstanceRef, Jvm, Result}; + +use crate::{RuntimeClassProto, RuntimeContext, classes::java::lang::Object}; + +use super::{HashMap, HashMapEntry, LinkedHashMapEntry}; + +const DEFAULT_INITIAL_CAPACITY: i32 = 16; +const DEFAULT_LOAD_FACTOR: f32 = 0.75; + +// public class java.util.LinkedHashMap +pub struct LinkedHashMap; + +impl LinkedHashMap { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/util/LinkedHashMap", + parent_class: Some("java/util/HashMap"), + interfaces: vec!["java/util/Map"], + methods: vec![ + JavaMethodProto::new("", "()V", Self::init_default, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("", "(I)V", Self::init_with_capacity, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("", "(IF)V", Self::init_with_capacity_and_load_factor, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "", + "(IFZ)V", + Self::init_with_capacity_load_factor_and_order, + MethodAccessFlags::PUBLIC, + ), + JavaMethodProto::new("", "(Ljava/util/Map;)V", Self::init_from_map, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("containsValue", "(Ljava/lang/Object;)Z", Self::contains_value, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("get", "(Ljava/lang/Object;)Ljava/lang/Object;", Self::get, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("clear", "()V", Self::clear, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "removeEldestEntry", + "(Ljava/util/Map$Entry;)Z", + Self::remove_eldest_entry, + MethodAccessFlags::PROTECTED, + ), + JavaMethodProto::new("initializeMap", "()V", Self::initialize_map, Default::default()), + JavaMethodProto::new( + "storeNewEntry", + "(ILjava/lang/Object;Ljava/lang/Object;I)V", + Self::store_new_entry, + Default::default(), + ), + JavaMethodProto::new( + "insertNewEntry", + "(ILjava/lang/Object;Ljava/lang/Object;I)V", + Self::insert_new_entry, + Default::default(), + ), + JavaMethodProto::new("keyIterator", "()Ljava/util/Iterator;", Self::key_iterator, Default::default()), + JavaMethodProto::new("valueIterator", "()Ljava/util/Iterator;", Self::value_iterator, Default::default()), + JavaMethodProto::new("entryIterator", "()Ljava/util/Iterator;", Self::entry_iterator, Default::default()), + ], + fields: vec![ + JavaFieldProto::new( + "header", + "Ljava/util/LinkedHashMap$Entry;", + FieldAccessFlags::PRIVATE | FieldAccessFlags::TRANSIENT, + ), + JavaFieldProto::new("accessOrder", "Z", FieldAccessFlags::PRIVATE | FieldAccessFlags::FINAL), + ], + access_flags: ClassAccessFlags::PUBLIC, + } + } + + async fn init_default(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result<()> { + jvm.invoke_special( + &this, + "java/util/LinkedHashMap", + "", + "(IFZ)V", + (DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR, false), + ) + .await + } + + async fn init_with_capacity(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, capacity: i32) -> Result<()> { + jvm.invoke_special( + &this, + "java/util/LinkedHashMap", + "", + "(IFZ)V", + (capacity, DEFAULT_LOAD_FACTOR, false), + ) + .await + } + + async fn init_with_capacity_and_load_factor( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + capacity: i32, + load_factor: f32, + ) -> Result<()> { + jvm.invoke_special(&this, "java/util/LinkedHashMap", "", "(IFZ)V", (capacity, load_factor, false)) + .await + } + + async fn init_with_capacity_load_factor_and_order( + jvm: &Jvm, + _: &mut RuntimeContext, + mut this: ClassInstanceRef, + capacity: i32, + load_factor: f32, + access_order: bool, + ) -> Result<()> { + let _: () = jvm + .invoke_special(&this, "java/util/HashMap", "", "(IF)V", (capacity, load_factor)) + .await?; + jvm.put_field(&mut this, "accessOrder", "Z", access_order).await + } + + async fn init_from_map(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, map: ClassInstanceRef) -> Result<()> { + if map.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "map").await); + } + let size: i32 = jvm.invoke_virtual(&map, "size", "()I", ()).await?; + let capacity = size.saturating_mul(2).max(DEFAULT_INITIAL_CAPACITY); + let _: () = jvm + .invoke_special( + &this, + "java/util/LinkedHashMap", + "", + "(IFZ)V", + (capacity, DEFAULT_LOAD_FACTOR, false), + ) + .await?; + + let entry_set: ClassInstanceRef = jvm.invoke_virtual(&map, "entrySet", "()Ljava/util/Set;", ()).await?; + let entries: ClassInstanceRef> = jvm.invoke_virtual(&entry_set, "toArray", "()[Ljava/lang/Object;", ()).await?; + let count = jvm.array_length(&entries).await?; + let mut hash_map: ClassInstanceRef = ClassInstanceRef::new(this.instance); + for entry in jvm.load_array::>(&entries, 0, count).await? { + let key: ClassInstanceRef = jvm.invoke_virtual(&entry, "getKey", "()Ljava/lang/Object;", ()).await?; + let value: ClassInstanceRef = jvm.invoke_virtual(&entry, "getValue", "()Ljava/lang/Object;", ()).await?; + HashMap::put_for_create(jvm, &mut hash_map, key, value).await?; + } + + Ok(()) + } + + async fn initialize_map(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef) -> Result<()> { + let null: ClassInstanceRef = None.into(); + let next: ClassInstanceRef = None.into(); + let mut header: ClassInstanceRef = jvm + .new_class( + "java/util/LinkedHashMap$Entry", + "(ILjava/lang/Object;Ljava/lang/Object;Ljava/util/HashMap$Entry;)V", + (-1, null.clone(), null, next), + ) + .await? + .into(); + let header_ref = header.clone(); + jvm.put_field(&mut header, "before", "Ljava/util/LinkedHashMap$Entry;", header_ref) + .await?; + let header_ref = header.clone(); + jvm.put_field(&mut header, "after", "Ljava/util/LinkedHashMap$Entry;", header_ref).await?; + jvm.put_field(&mut this, "header", "Ljava/util/LinkedHashMap$Entry;", header).await + } + + async fn contains_value(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, value: ClassInstanceRef) -> Result { + let header: ClassInstanceRef = jvm.get_field(&this, "header", "Ljava/util/LinkedHashMap$Entry;").await?; + let mut entry: ClassInstanceRef = jvm.get_field(&header, "after", "Ljava/util/LinkedHashMap$Entry;").await?; + while entry.identity() != header.identity() { + let entry_value: ClassInstanceRef = jvm.get_field(&entry, "value", "Ljava/lang/Object;").await?; + let equal = if value.is_null() { + entry_value.is_null() + } else if entry_value.is_null() { + false + } else { + jvm.invoke_virtual(&value, "equals", "(Ljava/lang/Object;)Z", (entry_value,)).await? + }; + if equal { + return Ok(true); + } + entry = jvm.get_field(&entry, "after", "Ljava/util/LinkedHashMap$Entry;").await?; + } + + Ok(false) + } + + async fn get(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, key: ClassInstanceRef) -> Result> { + let map: ClassInstanceRef = ClassInstanceRef::new(this.instance.clone()); + let entry = HashMap::find_entry(jvm, &map, &key).await?; + if entry.is_null() { + return Ok(None.into()); + } + let _: () = jvm.invoke_virtual(&entry, "onAccess", "(Ljava/util/HashMap;)V", (map,)).await?; + + jvm.get_field(&entry, "value", "Ljava/lang/Object;").await + } + + async fn clear(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result<()> { + let _: () = jvm.invoke_special(&this, "java/util/HashMap", "clear", "()V", ()).await?; + let mut header: ClassInstanceRef = jvm.get_field(&this, "header", "Ljava/util/LinkedHashMap$Entry;").await?; + let header_ref = header.clone(); + jvm.put_field(&mut header, "before", "Ljava/util/LinkedHashMap$Entry;", header_ref) + .await?; + let header_ref = header.clone(); + jvm.put_field(&mut header, "after", "Ljava/util/LinkedHashMap$Entry;", header_ref).await + } + + async fn store_new_entry( + jvm: &Jvm, + _: &mut RuntimeContext, + mut this: ClassInstanceRef, + hash: i32, + key: ClassInstanceRef, + value: ClassInstanceRef, + bucket_index: i32, + ) -> Result<()> { + let mut table: ClassInstanceRef> = jvm.get_field(&this, "table", "[Ljava/util/HashMap$Entry;").await?; + let existing = jvm + .load_array::>(&table, bucket_index as usize, 1) + .await? + .remove(0); + let mut entry: ClassInstanceRef = jvm + .new_class( + "java/util/LinkedHashMap$Entry", + "(ILjava/lang/Object;Ljava/lang/Object;Ljava/util/HashMap$Entry;)V", + (hash, key, value, existing), + ) + .await? + .into(); + let bucket_entry: ClassInstanceRef = ClassInstanceRef::new(entry.instance.clone()); + jvm.store_array(&mut table, bucket_index as usize, core::iter::once(bucket_entry)).await?; + + let mut header: ClassInstanceRef = jvm.get_field(&this, "header", "Ljava/util/LinkedHashMap$Entry;").await?; + let mut tail: ClassInstanceRef = jvm.get_field(&header, "before", "Ljava/util/LinkedHashMap$Entry;").await?; + jvm.put_field(&mut entry, "before", "Ljava/util/LinkedHashMap$Entry;", tail.clone()) + .await?; + jvm.put_field(&mut entry, "after", "Ljava/util/LinkedHashMap$Entry;", header.clone()) + .await?; + jvm.put_field(&mut tail, "after", "Ljava/util/LinkedHashMap$Entry;", entry.clone()) + .await?; + jvm.put_field(&mut header, "before", "Ljava/util/LinkedHashMap$Entry;", entry).await?; + + let size: i32 = jvm.get_field(&this, "size", "I").await?; + jvm.put_field(&mut this, "size", "I", size + 1).await + } + + async fn insert_new_entry( + jvm: &Jvm, + _: &mut RuntimeContext, + mut this: ClassInstanceRef, + hash: i32, + key: ClassInstanceRef, + value: ClassInstanceRef, + bucket_index: i32, + ) -> Result<()> { + let size: i32 = jvm.get_field(&this, "size", "I").await?; + let mod_count: i32 = jvm.get_field(&this, "modCount", "I").await?; + jvm.put_field(&mut this, "modCount", "I", mod_count.wrapping_add(1)).await?; + let _: () = jvm + .invoke_virtual( + &this, + "storeNewEntry", + "(ILjava/lang/Object;Ljava/lang/Object;I)V", + (hash, key, value, bucket_index), + ) + .await?; + + let header: ClassInstanceRef = jvm.get_field(&this, "header", "Ljava/util/LinkedHashMap$Entry;").await?; + let eldest: ClassInstanceRef = jvm.get_field(&header, "after", "Ljava/util/LinkedHashMap$Entry;").await?; + let eldest_entry: ClassInstanceRef = ClassInstanceRef::new(eldest.instance.clone()); + if jvm + .invoke_virtual::<_, bool>(&this, "removeEldestEntry", "(Ljava/util/Map$Entry;)Z", (eldest_entry,)) + .await? + { + let key: ClassInstanceRef = jvm.get_field(&eldest, "key", "Ljava/lang/Object;").await?; + let _: ClassInstanceRef = jvm + .invoke_virtual(&this, "remove", "(Ljava/lang/Object;)Ljava/lang/Object;", (key,)) + .await?; + } else { + let threshold: i32 = jvm.get_field(&this, "threshold", "I").await?; + if size >= threshold { + let mut map: ClassInstanceRef = ClassInstanceRef::new(this.instance.clone()); + HashMap::rehash(jvm, &mut map).await?; + } + } + + Ok(()) + } + + async fn remove_eldest_entry(_: &Jvm, _: &mut RuntimeContext, _: ClassInstanceRef, _: ClassInstanceRef) -> Result { + Ok(false) + } + + async fn key_iterator(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + Ok(jvm + .new_class("java/util/LinkedHashMap$KeyIterator", "(Ljava/util/LinkedHashMap;)V", (this,)) + .await? + .into()) + } + + async fn value_iterator(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + Ok(jvm + .new_class("java/util/LinkedHashMap$ValueIterator", "(Ljava/util/LinkedHashMap;)V", (this,)) + .await? + .into()) + } + + async fn entry_iterator(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + Ok(jvm + .new_class("java/util/LinkedHashMap$EntryIterator", "(Ljava/util/LinkedHashMap;)V", (this,)) + .await? + .into()) + } +} diff --git a/java_runtime/src/classes/java/util/linked_hash_map_entry.rs b/java_runtime/src/classes/java/util/linked_hash_map_entry.rs new file mode 100644 index 00000000..fc830f2d --- /dev/null +++ b/java_runtime/src/classes/java/util/linked_hash_map_entry.rs @@ -0,0 +1,88 @@ +use alloc::vec; + +use java_class_proto::{JavaFieldProto, JavaMethodProto}; +use jvm::{ClassInstanceRef, Jvm, Result}; + +use crate::{RuntimeClassProto, RuntimeContext, classes::java::lang::Object}; + +use super::{HashMap, HashMapEntry, LinkedHashMap}; + +// class java.util.LinkedHashMap$Entry +pub struct LinkedHashMapEntry; + +impl LinkedHashMapEntry { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/util/LinkedHashMap$Entry", + parent_class: Some("java/util/HashMap$Entry"), + interfaces: vec![], + methods: vec![ + JavaMethodProto::new( + "", + "(ILjava/lang/Object;Ljava/lang/Object;Ljava/util/HashMap$Entry;)V", + Self::init, + Default::default(), + ), + JavaMethodProto::new("onAccess", "(Ljava/util/HashMap;)V", Self::on_access, Default::default()), + JavaMethodProto::new("onRemoval", "(Ljava/util/HashMap;)V", Self::on_removal, Default::default()), + ], + fields: vec![ + JavaFieldProto::new("before", "Ljava/util/LinkedHashMap$Entry;", Default::default()), + JavaFieldProto::new("after", "Ljava/util/LinkedHashMap$Entry;", Default::default()), + ], + access_flags: Default::default(), + } + } + + async fn init( + jvm: &Jvm, + _: &mut RuntimeContext, + this: ClassInstanceRef, + hash: i32, + key: ClassInstanceRef, + value: ClassInstanceRef, + next: ClassInstanceRef, + ) -> Result<()> { + jvm.invoke_special( + &this, + "java/util/HashMap$Entry", + "", + "(ILjava/lang/Object;Ljava/lang/Object;Ljava/util/HashMap$Entry;)V", + (hash, key, value, next), + ) + .await + } + + async fn on_access(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, map: ClassInstanceRef) -> Result<()> { + let mut map: ClassInstanceRef = ClassInstanceRef::new(map.instance); + if !jvm.get_field::(&map, "accessOrder", "Z").await? { + return Ok(()); + } + + let mut before: ClassInstanceRef = jvm.get_field(&this, "before", "Ljava/util/LinkedHashMap$Entry;").await?; + let mut after: ClassInstanceRef = jvm.get_field(&this, "after", "Ljava/util/LinkedHashMap$Entry;").await?; + jvm.put_field(&mut before, "after", "Ljava/util/LinkedHashMap$Entry;", after.clone()) + .await?; + jvm.put_field(&mut after, "before", "Ljava/util/LinkedHashMap$Entry;", before).await?; + + let mut header: ClassInstanceRef = jvm.get_field(&map, "header", "Ljava/util/LinkedHashMap$Entry;").await?; + let mut tail: ClassInstanceRef = jvm.get_field(&header, "before", "Ljava/util/LinkedHashMap$Entry;").await?; + jvm.put_field(&mut this, "before", "Ljava/util/LinkedHashMap$Entry;", tail.clone()) + .await?; + jvm.put_field(&mut this, "after", "Ljava/util/LinkedHashMap$Entry;", header.clone()) + .await?; + jvm.put_field(&mut tail, "after", "Ljava/util/LinkedHashMap$Entry;", this.clone()).await?; + jvm.put_field(&mut header, "before", "Ljava/util/LinkedHashMap$Entry;", this).await?; + + let mod_count: i32 = jvm.get_field(&map, "modCount", "I").await?; + jvm.put_field(&mut map, "modCount", "I", mod_count.wrapping_add(1)).await + } + + async fn on_removal(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, _: ClassInstanceRef) -> Result<()> { + let mut before: ClassInstanceRef = jvm.get_field(&this, "before", "Ljava/util/LinkedHashMap$Entry;").await?; + let mut after: ClassInstanceRef = jvm.get_field(&this, "after", "Ljava/util/LinkedHashMap$Entry;").await?; + jvm.put_field(&mut before, "after", "Ljava/util/LinkedHashMap$Entry;", after.clone()) + .await?; + jvm.put_field(&mut after, "before", "Ljava/util/LinkedHashMap$Entry;", before).await + } +} diff --git a/java_runtime/src/classes/java/util/linked_hash_map_entry_iterator.rs b/java_runtime/src/classes/java/util/linked_hash_map_entry_iterator.rs new file mode 100644 index 00000000..5367b4df --- /dev/null +++ b/java_runtime/src/classes/java/util/linked_hash_map_entry_iterator.rs @@ -0,0 +1,53 @@ +use alloc::vec; + +use java_class_proto::JavaMethodProto; +use java_constants::MethodAccessFlags; +use jvm::{ClassInstanceRef, Jvm, Result}; + +use crate::{RuntimeClassProto, RuntimeContext, classes::java::lang::Object}; + +use super::{LinkedHashMap, LinkedHashMapEntry}; + +// class java.util.LinkedHashMap$EntryIterator +pub struct LinkedHashMapEntryIterator; + +impl LinkedHashMapEntryIterator { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/util/LinkedHashMap$EntryIterator", + parent_class: Some("java/util/LinkedHashMap$LinkedHashIterator"), + interfaces: vec![], + methods: vec![ + JavaMethodProto::new("", "(Ljava/util/LinkedHashMap;)V", Self::init, Default::default()), + JavaMethodProto::new("next", "()Ljava/lang/Object;", Self::next, MethodAccessFlags::PUBLIC), + ], + fields: vec![], + access_flags: Default::default(), + } + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, map: ClassInstanceRef) -> Result<()> { + jvm.invoke_special( + &this, + "java/util/LinkedHashMap$LinkedHashIterator", + "", + "(Ljava/util/LinkedHashMap;)V", + (map,), + ) + .await + } + + async fn next(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let entry: ClassInstanceRef = jvm + .invoke_special( + &this, + "java/util/LinkedHashMap$LinkedHashIterator", + "nextEntry", + "()Ljava/util/LinkedHashMap$Entry;", + (), + ) + .await?; + + Ok(ClassInstanceRef::new(entry.instance)) + } +} diff --git a/java_runtime/src/classes/java/util/linked_hash_map_key_iterator.rs b/java_runtime/src/classes/java/util/linked_hash_map_key_iterator.rs new file mode 100644 index 00000000..bad0e5be --- /dev/null +++ b/java_runtime/src/classes/java/util/linked_hash_map_key_iterator.rs @@ -0,0 +1,53 @@ +use alloc::vec; + +use java_class_proto::JavaMethodProto; +use java_constants::MethodAccessFlags; +use jvm::{ClassInstanceRef, Jvm, Result}; + +use crate::{RuntimeClassProto, RuntimeContext, classes::java::lang::Object}; + +use super::{LinkedHashMap, LinkedHashMapEntry}; + +// class java.util.LinkedHashMap$KeyIterator +pub struct LinkedHashMapKeyIterator; + +impl LinkedHashMapKeyIterator { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/util/LinkedHashMap$KeyIterator", + parent_class: Some("java/util/LinkedHashMap$LinkedHashIterator"), + interfaces: vec![], + methods: vec![ + JavaMethodProto::new("", "(Ljava/util/LinkedHashMap;)V", Self::init, Default::default()), + JavaMethodProto::new("next", "()Ljava/lang/Object;", Self::next, MethodAccessFlags::PUBLIC), + ], + fields: vec![], + access_flags: Default::default(), + } + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, map: ClassInstanceRef) -> Result<()> { + jvm.invoke_special( + &this, + "java/util/LinkedHashMap$LinkedHashIterator", + "", + "(Ljava/util/LinkedHashMap;)V", + (map,), + ) + .await + } + + async fn next(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let entry: ClassInstanceRef = jvm + .invoke_special( + &this, + "java/util/LinkedHashMap$LinkedHashIterator", + "nextEntry", + "()Ljava/util/LinkedHashMap$Entry;", + (), + ) + .await?; + + jvm.get_field(&entry, "key", "Ljava/lang/Object;").await + } +} diff --git a/java_runtime/src/classes/java/util/linked_hash_map_linked_hash_iterator.rs b/java_runtime/src/classes/java/util/linked_hash_map_linked_hash_iterator.rs new file mode 100644 index 00000000..ef6ee387 --- /dev/null +++ b/java_runtime/src/classes/java/util/linked_hash_map_linked_hash_iterator.rs @@ -0,0 +1,106 @@ +use alloc::vec; + +use java_class_proto::{JavaFieldProto, JavaMethodProto}; +use java_constants::{ClassAccessFlags, MethodAccessFlags}; +use jvm::{ClassInstanceRef, Jvm, Result}; + +use crate::{RuntimeClassProto, RuntimeContext, classes::java::lang::Object}; + +use super::{LinkedHashMap, LinkedHashMapEntry}; + +// abstract class java.util.LinkedHashMap$LinkedHashIterator +pub struct LinkedHashMapLinkedHashIterator; + +impl LinkedHashMapLinkedHashIterator { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/util/LinkedHashMap$LinkedHashIterator", + parent_class: Some("java/lang/Object"), + interfaces: vec!["java/util/Iterator"], + methods: vec![ + JavaMethodProto::new("", "(Ljava/util/LinkedHashMap;)V", Self::init, Default::default()), + JavaMethodProto::new("hasNext", "()Z", Self::has_next, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("remove", "()V", Self::remove, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("nextEntry", "()Ljava/util/LinkedHashMap$Entry;", Self::next_entry, Default::default()), + ], + fields: vec![ + JavaFieldProto::new("map", "Ljava/util/LinkedHashMap;", Default::default()), + JavaFieldProto::new("nextEntry", "Ljava/util/LinkedHashMap$Entry;", Default::default()), + JavaFieldProto::new("lastReturned", "Ljava/util/LinkedHashMap$Entry;", Default::default()), + JavaFieldProto::new("expectedModCount", "I", Default::default()), + ], + access_flags: ClassAccessFlags::ABSTRACT, + } + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, map: ClassInstanceRef) -> Result<()> { + let _: () = jvm.invoke_special(&this, "java/lang/Object", "", "()V", ()).await?; + let header: ClassInstanceRef = jvm.get_field(&map, "header", "Ljava/util/LinkedHashMap$Entry;").await?; + let next: ClassInstanceRef = jvm.get_field(&header, "after", "Ljava/util/LinkedHashMap$Entry;").await?; + let mod_count: i32 = jvm.get_field(&map, "modCount", "I").await?; + let last_returned: ClassInstanceRef = None.into(); + jvm.put_field(&mut this, "map", "Ljava/util/LinkedHashMap;", map).await?; + jvm.put_field(&mut this, "nextEntry", "Ljava/util/LinkedHashMap$Entry;", next).await?; + jvm.put_field(&mut this, "lastReturned", "Ljava/util/LinkedHashMap$Entry;", last_returned) + .await?; + jvm.put_field(&mut this, "expectedModCount", "I", mod_count).await + } + + async fn has_next(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let map: ClassInstanceRef = jvm.get_field(&this, "map", "Ljava/util/LinkedHashMap;").await?; + let header: ClassInstanceRef = jvm.get_field(&map, "header", "Ljava/util/LinkedHashMap$Entry;").await?; + let next: ClassInstanceRef = jvm.get_field(&this, "nextEntry", "Ljava/util/LinkedHashMap$Entry;").await?; + + Ok(next.identity() != header.identity()) + } + + async fn next_entry(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef) -> Result> { + let map: ClassInstanceRef = jvm.get_field(&this, "map", "Ljava/util/LinkedHashMap;").await?; + let expected_mod_count: i32 = jvm.get_field(&this, "expectedModCount", "I").await?; + let mod_count: i32 = jvm.get_field(&map, "modCount", "I").await?; + if expected_mod_count != mod_count { + return Err(jvm + .exception("java/util/ConcurrentModificationException", "LinkedHashMap modified during iteration") + .await); + } + + let header: ClassInstanceRef = jvm.get_field(&map, "header", "Ljava/util/LinkedHashMap$Entry;").await?; + let next: ClassInstanceRef = jvm.get_field(&this, "nextEntry", "Ljava/util/LinkedHashMap$Entry;").await?; + if next.identity() == header.identity() { + return Err(jvm + .exception("java/util/NoSuchElementException", "LinkedHashMap iterator exhausted") + .await); + } + let after: ClassInstanceRef = jvm.get_field(&next, "after", "Ljava/util/LinkedHashMap$Entry;").await?; + jvm.put_field(&mut this, "lastReturned", "Ljava/util/LinkedHashMap$Entry;", next.clone()) + .await?; + jvm.put_field(&mut this, "nextEntry", "Ljava/util/LinkedHashMap$Entry;", after).await?; + + Ok(next) + } + + async fn remove(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef) -> Result<()> { + let last_returned: ClassInstanceRef = jvm.get_field(&this, "lastReturned", "Ljava/util/LinkedHashMap$Entry;").await?; + if last_returned.is_null() { + return Err(jvm.exception("java/lang/IllegalStateException", "Iterator.remove").await); + } + let map: ClassInstanceRef = jvm.get_field(&this, "map", "Ljava/util/LinkedHashMap;").await?; + let expected_mod_count: i32 = jvm.get_field(&this, "expectedModCount", "I").await?; + let mod_count: i32 = jvm.get_field(&map, "modCount", "I").await?; + if expected_mod_count != mod_count { + return Err(jvm + .exception("java/util/ConcurrentModificationException", "LinkedHashMap modified during iteration") + .await); + } + + let key: ClassInstanceRef = jvm.get_field(&last_returned, "key", "Ljava/lang/Object;").await?; + let _: ClassInstanceRef = jvm + .invoke_virtual(&map, "remove", "(Ljava/lang/Object;)Ljava/lang/Object;", (key,)) + .await?; + let last_returned: ClassInstanceRef = None.into(); + jvm.put_field(&mut this, "lastReturned", "Ljava/util/LinkedHashMap$Entry;", last_returned) + .await?; + let mod_count: i32 = jvm.get_field(&map, "modCount", "I").await?; + jvm.put_field(&mut this, "expectedModCount", "I", mod_count).await + } +} diff --git a/java_runtime/src/classes/java/util/linked_hash_map_value_iterator.rs b/java_runtime/src/classes/java/util/linked_hash_map_value_iterator.rs new file mode 100644 index 00000000..cd8cc76b --- /dev/null +++ b/java_runtime/src/classes/java/util/linked_hash_map_value_iterator.rs @@ -0,0 +1,53 @@ +use alloc::vec; + +use java_class_proto::JavaMethodProto; +use java_constants::MethodAccessFlags; +use jvm::{ClassInstanceRef, Jvm, Result}; + +use crate::{RuntimeClassProto, RuntimeContext, classes::java::lang::Object}; + +use super::{LinkedHashMap, LinkedHashMapEntry}; + +// class java.util.LinkedHashMap$ValueIterator +pub struct LinkedHashMapValueIterator; + +impl LinkedHashMapValueIterator { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/util/LinkedHashMap$ValueIterator", + parent_class: Some("java/util/LinkedHashMap$LinkedHashIterator"), + interfaces: vec![], + methods: vec![ + JavaMethodProto::new("", "(Ljava/util/LinkedHashMap;)V", Self::init, Default::default()), + JavaMethodProto::new("next", "()Ljava/lang/Object;", Self::next, MethodAccessFlags::PUBLIC), + ], + fields: vec![], + access_flags: Default::default(), + } + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, map: ClassInstanceRef) -> Result<()> { + jvm.invoke_special( + &this, + "java/util/LinkedHashMap$LinkedHashIterator", + "", + "(Ljava/util/LinkedHashMap;)V", + (map,), + ) + .await + } + + async fn next(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let entry: ClassInstanceRef = jvm + .invoke_special( + &this, + "java/util/LinkedHashMap$LinkedHashIterator", + "nextEntry", + "()Ljava/util/LinkedHashMap$Entry;", + (), + ) + .await?; + + jvm.get_field(&entry, "value", "Ljava/lang/Object;").await + } +} diff --git a/java_runtime/src/loader.rs b/java_runtime/src/loader.rs index c3948f0f..dbc78f5b 100644 --- a/java_runtime/src/loader.rs +++ b/java_runtime/src/loader.rs @@ -187,6 +187,12 @@ pub fn get_runtime_class_proto(name: &str) -> Option { crate::classes::java::util::HashMapValueIterator::as_proto(), crate::classes::java::util::HashMapValues::as_proto(), crate::classes::java::util::HashSet::as_proto(), + crate::classes::java::util::LinkedHashMap::as_proto(), + crate::classes::java::util::LinkedHashMapEntry::as_proto(), + crate::classes::java::util::LinkedHashMapLinkedHashIterator::as_proto(), + crate::classes::java::util::LinkedHashMapEntryIterator::as_proto(), + crate::classes::java::util::LinkedHashMapKeyIterator::as_proto(), + crate::classes::java::util::LinkedHashMapValueIterator::as_proto(), crate::classes::java::util::Hashtable::as_proto(), crate::classes::java::util::HashtableEnumerator::as_proto(), crate::classes::java::util::HashtableEntry::as_proto(), diff --git a/java_runtime/tests/classes/java/util/mod.rs b/java_runtime/tests/classes/java/util/mod.rs index 0e75ddc7..adcd73ed 100644 --- a/java_runtime/tests/classes/java/util/mod.rs +++ b/java_runtime/tests/classes/java/util/mod.rs @@ -10,6 +10,7 @@ mod test_gregorian_calendar; mod test_hash_map; mod test_hash_set; mod test_hashtable; +mod test_linked_hash_map; mod test_list_views; mod test_locale; mod test_properties; diff --git a/java_runtime/tests/classes/java/util/test_linked_hash_map.rs b/java_runtime/tests/classes/java/util/test_linked_hash_map.rs new file mode 100644 index 00000000..24993fc3 --- /dev/null +++ b/java_runtime/tests/classes/java/util/test_linked_hash_map.rs @@ -0,0 +1,387 @@ +use alloc::{boxed::Box, collections::BTreeMap, vec, vec::Vec}; + +use java_class_proto::{JavaFieldProto, JavaMethodProto}; +use java_constants::{ClassAccessFlags, MethodAccessFlags}; +use java_runtime::{RuntimeClassProto, RuntimeContext, classes::java::lang::Object, get_runtime_class_proto}; +use jvm::{Array, ClassInstanceRef, JavaError, Jvm, Result, runtime::JavaLangString}; +use jvm_rust::ClassDefinitionImpl; + +use test_utils::{TestRuntime, create_test_jvm, test_jvm}; + +struct LimitedLinkedHashMap; + +impl LimitedLinkedHashMap { + fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "LimitedLinkedHashMap", + parent_class: Some("java/util/LinkedHashMap"), + interfaces: vec![], + methods: vec![ + JavaMethodProto::new("", "(I)V", Self::init, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "removeEldestEntry", + "(Ljava/util/Map$Entry;)Z", + Self::remove_eldest_entry, + MethodAccessFlags::PROTECTED, + ), + ], + fields: vec![ + JavaFieldProto::new("limit", "I", Default::default()), + JavaFieldProto::new("callbacks", "I", Default::default()), + ], + access_flags: ClassAccessFlags::PUBLIC, + } + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, limit: i32) -> Result<()> { + let _: () = jvm.invoke_special(&this, "java/util/LinkedHashMap", "", "()V", ()).await?; + jvm.put_field(&mut this, "limit", "I", limit).await?; + jvm.put_field(&mut this, "callbacks", "I", 0).await + } + + async fn remove_eldest_entry(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, _: ClassInstanceRef) -> Result { + let callbacks: i32 = jvm.get_field(&this, "callbacks", "I").await?; + jvm.put_field(&mut this, "callbacks", "I", callbacks + 1).await?; + let size: i32 = jvm.invoke_virtual(&this, "size", "()I", ()).await?; + let limit: i32 = jvm.get_field(&this, "limit", "I").await?; + + Ok(size > limit) + } +} + +async fn view_strings(jvm: &Jvm, map: &ClassInstanceRef, method: &str, descriptor: &str) -> Result>> { + let view: ClassInstanceRef = jvm.invoke_virtual(map, method, descriptor, ()).await?; + let iterator: ClassInstanceRef = jvm.invoke_virtual(&view, "iterator", "()Ljava/util/Iterator;", ()).await?; + let mut values = Vec::new(); + while jvm.invoke_virtual::<_, bool>(&iterator, "hasNext", "()Z", ()).await? { + let value: ClassInstanceRef = jvm.invoke_virtual(&iterator, "next", "()Ljava/lang/Object;", ()).await?; + values.push(if value.is_null() { + None + } else { + Some(JavaLangString::to_rust_string(jvm, &value).await?) + }); + } + + Ok(values) +} + +async fn entry_strings(jvm: &Jvm, map: &ClassInstanceRef) -> Result, Option)>> { + let view: ClassInstanceRef = jvm.invoke_virtual(map, "entrySet", "()Ljava/util/Set;", ()).await?; + let iterator: ClassInstanceRef = jvm.invoke_virtual(&view, "iterator", "()Ljava/util/Iterator;", ()).await?; + let mut entries = Vec::new(); + while jvm.invoke_virtual::<_, bool>(&iterator, "hasNext", "()Z", ()).await? { + let entry: ClassInstanceRef = jvm.invoke_virtual(&iterator, "next", "()Ljava/lang/Object;", ()).await?; + let key: ClassInstanceRef = jvm.invoke_virtual(&entry, "getKey", "()Ljava/lang/Object;", ()).await?; + let value: ClassInstanceRef = jvm.invoke_virtual(&entry, "getValue", "()Ljava/lang/Object;", ()).await?; + entries.push(( + if key.is_null() { + None + } else { + Some(JavaLangString::to_rust_string(jvm, &key).await?) + }, + if value.is_null() { + None + } else { + Some(JavaLangString::to_rust_string(jvm, &value).await?) + }, + )); + } + + Ok(entries) +} + +#[tokio::test] +async fn linked_hash_map_exposes_the_cdc_11_class_shape_and_constructor_validation() -> Result<()> { + let proto = get_runtime_class_proto("java/util/LinkedHashMap").expect("LinkedHashMap must be registered"); + assert_eq!(proto.parent_class, Some("java/util/HashMap")); + assert_eq!(proto.interfaces, vec!["java/util/Map"]); + assert_eq!(proto.access_flags, ClassAccessFlags::PUBLIC); + for descriptor in ["()V", "(I)V", "(IF)V", "(IFZ)V", "(Ljava/util/Map;)V"] { + let method = proto + .methods + .iter() + .find(|method| method.name == "" && method.descriptor == descriptor) + .unwrap_or_else(|| panic!("missing LinkedHashMap{descriptor}")); + assert_eq!(method.access_flags, MethodAccessFlags::PUBLIC); + } + for (name, descriptor, flags) in [ + ("containsValue", "(Ljava/lang/Object;)Z", MethodAccessFlags::PUBLIC), + ("get", "(Ljava/lang/Object;)Ljava/lang/Object;", MethodAccessFlags::PUBLIC), + ("clear", "()V", MethodAccessFlags::PUBLIC), + ("removeEldestEntry", "(Ljava/util/Map$Entry;)Z", MethodAccessFlags::PROTECTED), + ] { + let method = proto + .methods + .iter() + .find(|method| method.name == name && method.descriptor == descriptor) + .unwrap_or_else(|| panic!("missing {name}{descriptor}")); + assert_eq!(method.access_flags, flags); + } + + let jvm = test_jvm().await?; + let map = jvm.new_class("java/util/LinkedHashMap", "()V", ()).await?; + assert!(jvm.is_instance(&*map, "java/util/HashMap")); + assert!(jvm.is_instance(&*map, "java/util/Map")); + + for result in [ + jvm.new_class("java/util/LinkedHashMap", "(I)V", (-1,)).await, + jvm.new_class("java/util/LinkedHashMap", "(IF)V", (1, 0.0f32)).await, + jvm.new_class("java/util/LinkedHashMap", "(IF)V", (1, -1.0f32)).await, + jvm.new_class("java/util/LinkedHashMap", "(IFZ)V", (1, f32::NAN, false)).await, + ] { + let Err(JavaError::JavaException(exception)) = result else { + panic!("invalid LinkedHashMap constructor arguments must fail"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/IllegalArgumentException")); + } + + Ok(()) +} + +#[tokio::test] +async fn linked_hash_map_preserves_insertion_order_across_views_nulls_and_rehash() -> Result<()> { + let jvm = test_jvm().await?; + let map: ClassInstanceRef = jvm.new_class("java/util/LinkedHashMap", "(IF)V", (1, 0.75f32)).await?.into(); + + for index in 0..24 { + let key = JavaLangString::from_rust_string(&jvm, &format!("k{index:02}")).await?; + let value = JavaLangString::from_rust_string(&jvm, &format!("v{index:02}")).await?; + let _: ClassInstanceRef = jvm + .invoke_virtual(&map, "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", (key, value)) + .await?; + } + let replacement_key = JavaLangString::from_rust_string(&jvm, "k05").await?; + let replacement = JavaLangString::from_rust_string(&jvm, "changed").await?; + let _: ClassInstanceRef = jvm + .invoke_virtual( + &map, + "put", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", + (replacement_key, replacement), + ) + .await?; + let null: ClassInstanceRef = None.into(); + let _: ClassInstanceRef = jvm + .invoke_virtual( + &map, + "put", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", + (null.clone(), null), + ) + .await?; + + let mut expected_keys: Vec> = (0..24).map(|index| Some(format!("k{index:02}"))).collect(); + expected_keys.push(None); + assert_eq!(view_strings(&jvm, &map, "keySet", "()Ljava/util/Set;").await?, expected_keys); + + let values = view_strings(&jvm, &map, "values", "()Ljava/util/Collection;").await?; + assert_eq!(values[5], Some("changed".into())); + assert_eq!(values.last(), Some(&None)); + let entries = entry_strings(&jvm, &map).await?; + assert_eq!(entries[5], (Some("k05".into()), Some("changed".into()))); + assert_eq!(entries.last(), Some(&(None, None))); + + let keys: ClassInstanceRef = jvm.invoke_virtual(&map, "keySet", "()Ljava/util/Set;", ()).await?; + let iterator: ClassInstanceRef = jvm.invoke_virtual(&keys, "iterator", "()Ljava/util/Iterator;", ()).await?; + let key = JavaLangString::from_rust_string(&jvm, "k00").await?; + let _: ClassInstanceRef = jvm.invoke_virtual(&map, "get", "(Ljava/lang/Object;)Ljava/lang/Object;", (key,)).await?; + let first: ClassInstanceRef = jvm.invoke_virtual(&iterator, "next", "()Ljava/lang/Object;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &first).await?, "k00"); + + let text: ClassInstanceRef = jvm.invoke_virtual(&map, "toString", "()Ljava/lang/String;", ()).await?; + let text = JavaLangString::to_rust_string(&jvm, &text).await?; + assert!(text.starts_with("{k00=v00, k01=v01, k02=v02")); + assert!(text.ends_with(", null=null}")); + + Ok(()) +} + +#[tokio::test] +async fn linked_hash_map_access_order_tracks_only_documented_accesses() -> Result<()> { + let jvm = test_jvm().await?; + let map: ClassInstanceRef = jvm.new_class("java/util/LinkedHashMap", "(IFZ)V", (4, 0.75f32, true)).await?.into(); + for key in ["a", "b", "c"] { + let value = JavaLangString::from_rust_string(&jvm, key).await?; + let key = JavaLangString::from_rust_string(&jvm, key).await?; + let _: ClassInstanceRef = jvm + .invoke_virtual(&map, "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", (key, value)) + .await?; + } + + let keys: ClassInstanceRef = jvm.invoke_virtual(&map, "keySet", "()Ljava/util/Set;", ()).await?; + let iterator: ClassInstanceRef = jvm.invoke_virtual(&keys, "iterator", "()Ljava/util/Iterator;", ()).await?; + let missing = JavaLangString::from_rust_string(&jvm, "missing").await?; + let missing_value: ClassInstanceRef = jvm + .invoke_virtual(&map, "get", "(Ljava/lang/Object;)Ljava/lang/Object;", (missing,)) + .await?; + assert!(missing_value.is_null()); + let first: ClassInstanceRef = jvm.invoke_virtual(&iterator, "next", "()Ljava/lang/Object;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &first).await?, "a"); + + let iterator: ClassInstanceRef = jvm.invoke_virtual(&keys, "iterator", "()Ljava/util/Iterator;", ()).await?; + let key = JavaLangString::from_rust_string(&jvm, "a").await?; + let _: ClassInstanceRef = jvm.invoke_virtual(&map, "get", "(Ljava/lang/Object;)Ljava/lang/Object;", (key,)).await?; + let result: Result> = jvm.invoke_virtual(&iterator, "next", "()Ljava/lang/Object;", ()).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("access-order get must invalidate an existing iterator"); + }; + assert!(jvm.is_instance(&*exception, "java/util/ConcurrentModificationException")); + assert_eq!( + view_strings(&jvm, &map, "keySet", "()Ljava/util/Set;").await?, + vec![Some("b".into()), Some("c".into()), Some("a".into())] + ); + + let key = JavaLangString::from_rust_string(&jvm, "b").await?; + assert!( + jvm.invoke_virtual::<_, bool>(&map, "containsKey", "(Ljava/lang/Object;)Z", (key,)) + .await? + ); + assert_eq!( + view_strings(&jvm, &map, "keySet", "()Ljava/util/Set;").await?, + vec![Some("b".into()), Some("c".into()), Some("a".into())] + ); + + let key = JavaLangString::from_rust_string(&jvm, "b").await?; + let value = JavaLangString::from_rust_string(&jvm, "B").await?; + let _: ClassInstanceRef = jvm + .invoke_virtual(&map, "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", (key, value)) + .await?; + assert_eq!( + view_strings(&jvm, &map, "keySet", "()Ljava/util/Set;").await?, + vec![Some("c".into()), Some("a".into()), Some("b".into())] + ); + + let source: ClassInstanceRef = jvm.new_class("java/util/LinkedHashMap", "()V", ()).await?.into(); + for key in ["a", "d"] { + let value = JavaLangString::from_rust_string(&jvm, key).await?; + let key = JavaLangString::from_rust_string(&jvm, key).await?; + let _: ClassInstanceRef = jvm + .invoke_virtual(&source, "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", (key, value)) + .await?; + } + let _: () = jvm.invoke_virtual(&map, "putAll", "(Ljava/util/Map;)V", (source,)).await?; + assert_eq!( + view_strings(&jvm, &map, "keySet", "()Ljava/util/Set;").await?, + vec![Some("c".into()), Some("b".into()), Some("a".into()), Some("d".into())] + ); + + Ok(()) +} + +#[tokio::test] +async fn linked_hash_map_views_and_iterators_remove_in_order_and_fail_fast() -> Result<()> { + let jvm = test_jvm().await?; + let map: ClassInstanceRef = jvm.new_class("java/util/LinkedHashMap", "()V", ()).await?.into(); + for (key, value) in [("a", "same"), ("b", "same"), ("c", "other")] { + let key = JavaLangString::from_rust_string(&jvm, key).await?; + let value = JavaLangString::from_rust_string(&jvm, value).await?; + let _: ClassInstanceRef = jvm + .invoke_virtual(&map, "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", (key, value)) + .await?; + } + + let values: ClassInstanceRef = jvm.invoke_virtual(&map, "values", "()Ljava/util/Collection;", ()).await?; + let same = JavaLangString::from_rust_string(&jvm, "same").await?; + assert!(jvm.invoke_virtual::<_, bool>(&values, "remove", "(Ljava/lang/Object;)Z", (same,)).await?); + assert_eq!( + view_strings(&jvm, &map, "keySet", "()Ljava/util/Set;").await?, + vec![Some("b".into()), Some("c".into())] + ); + + let keys: ClassInstanceRef = jvm.invoke_virtual(&map, "keySet", "()Ljava/util/Set;", ()).await?; + let iterator: ClassInstanceRef = jvm.invoke_virtual(&keys, "iterator", "()Ljava/util/Iterator;", ()).await?; + let first: ClassInstanceRef = jvm.invoke_virtual(&iterator, "next", "()Ljava/lang/Object;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &first).await?, "b"); + let _: () = jvm.invoke_virtual(&iterator, "remove", "()V", ()).await?; + let result: Result<()> = jvm.invoke_virtual(&iterator, "remove", "()V", ()).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("repeated iterator remove must fail"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/IllegalStateException")); + assert_eq!(view_strings(&jvm, &map, "keySet", "()Ljava/util/Set;").await?, vec![Some("c".into())]); + + let iterator: ClassInstanceRef = jvm.invoke_virtual(&keys, "iterator", "()Ljava/util/Iterator;", ()).await?; + let key = JavaLangString::from_rust_string(&jvm, "d").await?; + let value = JavaLangString::from_rust_string(&jvm, "other").await?; + let _: ClassInstanceRef = jvm + .invoke_virtual(&map, "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", (key, value)) + .await?; + let result: Result> = jvm.invoke_virtual(&iterator, "next", "()Ljava/lang/Object;", ()).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("structural modification must invalidate iterator"); + }; + assert!(jvm.is_instance(&*exception, "java/util/ConcurrentModificationException")); + + let _: () = jvm.invoke_virtual(&map, "clear", "()V", ()).await?; + assert!(view_strings(&jvm, &map, "keySet", "()Ljava/util/Set;").await?.is_empty()); + let key = JavaLangString::from_rust_string(&jvm, "reused").await?; + let value = JavaLangString::from_rust_string(&jvm, "value").await?; + let _: ClassInstanceRef = jvm + .invoke_virtual(&map, "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", (key, value)) + .await?; + assert_eq!( + view_strings(&jvm, &map, "keySet", "()Ljava/util/Set;").await?, + vec![Some("reused".into())] + ); + + let keys: ClassInstanceRef = jvm.invoke_virtual(&map, "keySet", "()Ljava/util/Set;", ()).await?; + let iterator: ClassInstanceRef = jvm.invoke_virtual(&keys, "iterator", "()Ljava/util/Iterator;", ()).await?; + let _: ClassInstanceRef = jvm.invoke_virtual(&iterator, "next", "()Ljava/lang/Object;", ()).await?; + let result: Result> = jvm.invoke_virtual(&iterator, "next", "()Ljava/lang/Object;", ()).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("exhausted iterator must fail"); + }; + assert!(jvm.is_instance(&*exception, "java/util/NoSuchElementException")); + + Ok(()) +} + +#[tokio::test] +async fn linked_hash_map_copy_and_remove_eldest_entry_preserve_policy_and_order() -> Result<()> { + let runtime = TestRuntime::new(BTreeMap::new()); + let jvm = create_test_jvm(runtime.clone()).await?; + jvm.register_class( + Box::new(ClassDefinitionImpl::from_class_proto( + LimitedLinkedHashMap::as_proto(), + Box::new(runtime) as Box<_>, + )), + None, + ) + .await?; + + let limited: ClassInstanceRef = jvm.new_class("LimitedLinkedHashMap", "(I)V", (2,)).await?.into(); + for key in ["a", "b", "c"] { + let key = JavaLangString::from_rust_string(&jvm, key).await?; + let value = key.clone(); + let _: ClassInstanceRef = jvm + .invoke_virtual(&limited, "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", (key, value)) + .await?; + } + assert_eq!( + view_strings(&jvm, &limited, "keySet", "()Ljava/util/Set;").await?, + vec![Some("b".into()), Some("c".into())] + ); + assert_eq!(jvm.get_field::(&limited, "callbacks", "I").await?, 3); + + let key = JavaLangString::from_rust_string(&jvm, "c").await?; + let value = JavaLangString::from_rust_string(&jvm, "C").await?; + let _: ClassInstanceRef = jvm + .invoke_virtual(&limited, "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", (key, value)) + .await?; + assert_eq!(jvm.get_field::(&limited, "callbacks", "I").await?, 3); + + let copy: ClassInstanceRef = jvm.new_class("java/util/LinkedHashMap", "(Ljava/util/Map;)V", (limited,)).await?.into(); + assert_eq!( + view_strings(&jvm, ©, "keySet", "()Ljava/util/Set;").await?, + vec![Some("b".into()), Some("c".into())] + ); + assert_eq!(entry_strings(&jvm, ©).await?[1], (Some("c".into()), Some("C".into()))); + + let key_set: ClassInstanceRef = jvm.invoke_virtual(©, "keySet", "()Ljava/util/Set;", ()).await?; + let array: ClassInstanceRef> = jvm.invoke_virtual(&key_set, "toArray", "()[Ljava/lang/Object;", ()).await?; + let keys = jvm.load_array::>(&array, 0, 2).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &keys[0]).await?, "b"); + assert_eq!(JavaLangString::to_rust_string(&jvm, &keys[1]).await?, "c"); + + Ok(()) +} From d934ed5215c3100bfc9e703acc1f9e82435d4aef Mon Sep 17 00:00:00 2001 From: Inseok Lee Date: Sun, 9 Aug 2026 17:36:41 +0900 Subject: [PATCH 2/3] Fix LinkedHashMap review findings --- .../src/classes/java/util/hash_map.rs | 21 ++- .../classes/java/util/hash_map_entry_set.rs | 3 +- .../src/classes/java/util/hash_map_key_set.rs | 3 +- .../src/classes/java/util/hash_map_values.rs | 7 +- .../src/classes/java/util/linked_hash_map.rs | 15 ++- .../java/util/linked_hash_map_entry.rs | 8 +- .../classes/java/util/test_linked_hash_map.rs | 15 +++ jvm/src/class_loader.rs | 27 +++- jvm/src/jvm.rs | 126 ++++++++++++++---- jvm_rust/src/interpreter.rs | 4 +- .../VirtualDispatch$HookCollisionMap.class | Bin 0 -> 407 bytes test_data/VirtualDispatch.class | Bin 0 -> 977 bytes test_data/VirtualDispatch.txt | 4 + test_data/dispatch/base/PackageBase.class | Bin 0 -> 477 bytes test_data/dispatch/base/PackageMiddle.class | Bin 0 -> 424 bytes test_data/dispatch/foreign/ForeignChild.class | Bin 0 -> 428 bytes test_data/src/VirtualDispatch.java | 25 ++++ test_data/src/dispatch/base/PackageBase.java | 11 ++ .../src/dispatch/base/PackageMiddle.java | 7 + .../src/dispatch/foreign/ForeignChild.java | 9 ++ 20 files changed, 237 insertions(+), 48 deletions(-) create mode 100644 test_data/VirtualDispatch$HookCollisionMap.class create mode 100644 test_data/VirtualDispatch.class create mode 100644 test_data/VirtualDispatch.txt create mode 100644 test_data/dispatch/base/PackageBase.class create mode 100644 test_data/dispatch/base/PackageMiddle.class create mode 100644 test_data/dispatch/foreign/ForeignChild.class create mode 100644 test_data/src/VirtualDispatch.java create mode 100644 test_data/src/dispatch/base/PackageBase.java create mode 100644 test_data/src/dispatch/base/PackageMiddle.java create mode 100644 test_data/src/dispatch/foreign/ForeignChild.java diff --git a/java_runtime/src/classes/java/util/hash_map.rs b/java_runtime/src/classes/java/util/hash_map.rs index e43d0271..31470047 100644 --- a/java_runtime/src/classes/java/util/hash_map.rs +++ b/java_runtime/src/classes/java/util/hash_map.rs @@ -163,7 +163,9 @@ impl HashMap { .await?; jvm.put_field(&mut this, "loadFactor", "F", load_factor).await?; jvm.put_field(&mut this, "modCount", "I", 0).await?; - let _: () = jvm.invoke_virtual(&this, "initializeMap", "()V", ()).await?; + let _: () = jvm + .invoke_virtual_with_owner(&this, "java/util/HashMap", "initializeMap", "()V", ()) + .await?; Ok(()) } @@ -268,7 +270,9 @@ impl HashMap { if Self::keys_equal(jvm, &key, &entry_key).await? { let old_value: ClassInstanceRef = jvm.get_field(&entry, "value", "Ljava/lang/Object;").await?; jvm.put_field(&mut entry, "value", "Ljava/lang/Object;", value).await?; - let _: () = jvm.invoke_virtual(&entry, "onAccess", "(Ljava/util/HashMap;)V", (this.clone(),)).await?; + let _: () = jvm + .invoke_virtual_with_owner(&entry, "java/util/HashMap$Entry", "onAccess", "(Ljava/util/HashMap;)V", (this.clone(),)) + .await?; return Ok(old_value); } } @@ -277,8 +281,9 @@ impl HashMap { } let _: () = jvm - .invoke_virtual( + .invoke_virtual_with_owner( &this, + "java/util/HashMap", "insertNewEntry", "(ILjava/lang/Object;Ljava/lang/Object;I)V", (key_hash, key, value, bucket_index as i32), @@ -326,7 +331,9 @@ impl HashMap { } let old_value: ClassInstanceRef = jvm.get_field(&entry, "value", "Ljava/lang/Object;").await?; - let _: () = jvm.invoke_virtual(&entry, "onRemoval", "(Ljava/util/HashMap;)V", (this.clone(),)).await?; + let _: () = jvm + .invoke_virtual_with_owner(&entry, "java/util/HashMap$Entry", "onRemoval", "(Ljava/util/HashMap;)V", (this.clone(),)) + .await?; let null_entry: ClassInstanceRef = None.into(); jvm.put_field(&mut entry, "next", "Ljava/util/HashMap$Entry;", null_entry).await?; @@ -403,8 +410,9 @@ impl HashMap { let mod_count: i32 = jvm.get_field(&this, "modCount", "I").await?; jvm.put_field(&mut this, "modCount", "I", mod_count.wrapping_add(1)).await?; let _: () = jvm - .invoke_virtual( + .invoke_virtual_with_owner( &this, + "java/util/HashMap", "storeNewEntry", "(ILjava/lang/Object;Ljava/lang/Object;I)V", (hash, key, value, bucket_index), @@ -484,8 +492,9 @@ impl HashMap { Self::ensure_table_for_insert(jvm, this).await?; let table: ClassInstanceRef> = jvm.get_field(this, "table", "[Ljava/util/HashMap$Entry;").await?; let bucket_index = Self::bucket_index(hash, jvm.array_length(&table).await?); - jvm.invoke_virtual( + jvm.invoke_virtual_with_owner( this, + "java/util/HashMap", "storeNewEntry", "(ILjava/lang/Object;Ljava/lang/Object;I)V", (hash, key, value, bucket_index as i32), diff --git a/java_runtime/src/classes/java/util/hash_map_entry_set.rs b/java_runtime/src/classes/java/util/hash_map_entry_set.rs index 4f51083e..b1823826 100644 --- a/java_runtime/src/classes/java/util/hash_map_entry_set.rs +++ b/java_runtime/src/classes/java/util/hash_map_entry_set.rs @@ -127,7 +127,8 @@ impl HashMapEntrySet { let map: ClassInstanceRef = jvm.get_field(&this, "map", "Ljava/util/HashMap;").await?; - jvm.invoke_virtual(&map, "entryIterator", "()Ljava/util/Iterator;", ()).await + jvm.invoke_virtual_with_owner(&map, "java/util/HashMap", "entryIterator", "()Ljava/util/Iterator;", ()) + .await } async fn object_equals(jvm: &Jvm, left: &ClassInstanceRef, right: &ClassInstanceRef) -> Result { diff --git a/java_runtime/src/classes/java/util/hash_map_key_set.rs b/java_runtime/src/classes/java/util/hash_map_key_set.rs index cc083a2a..3061e999 100644 --- a/java_runtime/src/classes/java/util/hash_map_key_set.rs +++ b/java_runtime/src/classes/java/util/hash_map_key_set.rs @@ -92,6 +92,7 @@ impl HashMapKeySet { let map: ClassInstanceRef = jvm.get_field(&this, "map", "Ljava/util/HashMap;").await?; - jvm.invoke_virtual(&map, "keyIterator", "()Ljava/util/Iterator;", ()).await + jvm.invoke_virtual_with_owner(&map, "java/util/HashMap", "keyIterator", "()Ljava/util/Iterator;", ()) + .await } } diff --git a/java_runtime/src/classes/java/util/hash_map_values.rs b/java_runtime/src/classes/java/util/hash_map_values.rs index 323811d6..651eced0 100644 --- a/java_runtime/src/classes/java/util/hash_map_values.rs +++ b/java_runtime/src/classes/java/util/hash_map_values.rs @@ -68,7 +68,9 @@ impl HashMapValues { tracing::debug!("java.util.HashMap$Values::remove({this:?}, {value:?})"); let map: ClassInstanceRef = jvm.get_field(&this, "map", "Ljava/util/HashMap;").await?; - let iterator: ClassInstanceRef = jvm.invoke_virtual(&map, "entryIterator", "()Ljava/util/Iterator;", ()).await?; + let iterator: ClassInstanceRef = jvm + .invoke_virtual_with_owner(&map, "java/util/HashMap", "entryIterator", "()Ljava/util/Iterator;", ()) + .await?; while jvm.invoke_virtual::<_, bool>(&iterator, "hasNext", "()Z", ()).await? { let entry: ClassInstanceRef = jvm.invoke_virtual(&iterator, "next", "()Ljava/lang/Object;", ()).await?; let entry_value: ClassInstanceRef = jvm.invoke_virtual(&entry, "getValue", "()Ljava/lang/Object;", ()).await?; @@ -104,6 +106,7 @@ impl HashMapValues { let map: ClassInstanceRef = jvm.get_field(&this, "map", "Ljava/util/HashMap;").await?; - jvm.invoke_virtual(&map, "valueIterator", "()Ljava/util/Iterator;", ()).await + jvm.invoke_virtual_with_owner(&map, "java/util/HashMap", "valueIterator", "()Ljava/util/Iterator;", ()) + .await } } diff --git a/java_runtime/src/classes/java/util/linked_hash_map.rs b/java_runtime/src/classes/java/util/linked_hash_map.rs index 92f7b1d3..71a3036a 100644 --- a/java_runtime/src/classes/java/util/linked_hash_map.rs +++ b/java_runtime/src/classes/java/util/linked_hash_map.rs @@ -191,7 +191,9 @@ impl LinkedHashMap { if entry.is_null() { return Ok(None.into()); } - let _: () = jvm.invoke_virtual(&entry, "onAccess", "(Ljava/util/HashMap;)V", (map,)).await?; + let _: () = jvm + .invoke_virtual_with_owner(&entry, "java/util/HashMap$Entry", "onAccess", "(Ljava/util/HashMap;)V", (map,)) + .await?; jvm.get_field(&entry, "value", "Ljava/lang/Object;").await } @@ -258,8 +260,9 @@ impl LinkedHashMap { let mod_count: i32 = jvm.get_field(&this, "modCount", "I").await?; jvm.put_field(&mut this, "modCount", "I", mod_count.wrapping_add(1)).await?; let _: () = jvm - .invoke_virtual( + .invoke_virtual_with_owner( &this, + "java/util/LinkedHashMap", "storeNewEntry", "(ILjava/lang/Object;Ljava/lang/Object;I)V", (hash, key, value, bucket_index), @@ -270,7 +273,13 @@ impl LinkedHashMap { let eldest: ClassInstanceRef = jvm.get_field(&header, "after", "Ljava/util/LinkedHashMap$Entry;").await?; let eldest_entry: ClassInstanceRef = ClassInstanceRef::new(eldest.instance.clone()); if jvm - .invoke_virtual::<_, bool>(&this, "removeEldestEntry", "(Ljava/util/Map$Entry;)Z", (eldest_entry,)) + .invoke_virtual_with_owner::<_, bool>( + &this, + "java/util/LinkedHashMap", + "removeEldestEntry", + "(Ljava/util/Map$Entry;)Z", + (eldest_entry,), + ) .await? { let key: ClassInstanceRef = jvm.get_field(&eldest, "key", "Ljava/lang/Object;").await?; diff --git a/java_runtime/src/classes/java/util/linked_hash_map_entry.rs b/java_runtime/src/classes/java/util/linked_hash_map_entry.rs index fc830f2d..e249c35c 100644 --- a/java_runtime/src/classes/java/util/linked_hash_map_entry.rs +++ b/java_runtime/src/classes/java/util/linked_hash_map_entry.rs @@ -59,14 +59,18 @@ impl LinkedHashMapEntry { return Ok(()); } + let mut header: ClassInstanceRef = jvm.get_field(&map, "header", "Ljava/util/LinkedHashMap$Entry;").await?; + let mut tail: ClassInstanceRef = jvm.get_field(&header, "before", "Ljava/util/LinkedHashMap$Entry;").await?; + if this.identity() == tail.identity() { + return Ok(()); + } + let mut before: ClassInstanceRef = jvm.get_field(&this, "before", "Ljava/util/LinkedHashMap$Entry;").await?; let mut after: ClassInstanceRef = jvm.get_field(&this, "after", "Ljava/util/LinkedHashMap$Entry;").await?; jvm.put_field(&mut before, "after", "Ljava/util/LinkedHashMap$Entry;", after.clone()) .await?; jvm.put_field(&mut after, "before", "Ljava/util/LinkedHashMap$Entry;", before).await?; - let mut header: ClassInstanceRef = jvm.get_field(&map, "header", "Ljava/util/LinkedHashMap$Entry;").await?; - let mut tail: ClassInstanceRef = jvm.get_field(&header, "before", "Ljava/util/LinkedHashMap$Entry;").await?; jvm.put_field(&mut this, "before", "Ljava/util/LinkedHashMap$Entry;", tail.clone()) .await?; jvm.put_field(&mut this, "after", "Ljava/util/LinkedHashMap$Entry;", header.clone()) diff --git a/java_runtime/tests/classes/java/util/test_linked_hash_map.rs b/java_runtime/tests/classes/java/util/test_linked_hash_map.rs index 24993fc3..ae5ff343 100644 --- a/java_runtime/tests/classes/java/util/test_linked_hash_map.rs +++ b/java_runtime/tests/classes/java/util/test_linked_hash_map.rs @@ -209,6 +209,21 @@ async fn linked_hash_map_access_order_tracks_only_documented_accesses() -> Resul } let keys: ClassInstanceRef = jvm.invoke_virtual(&map, "keySet", "()Ljava/util/Set;", ()).await?; + let iterator: ClassInstanceRef = jvm.invoke_virtual(&keys, "iterator", "()Ljava/util/Iterator;", ()).await?; + let tail = JavaLangString::from_rust_string(&jvm, "c").await?; + let _: ClassInstanceRef = jvm.invoke_virtual(&map, "get", "(Ljava/lang/Object;)Ljava/lang/Object;", (tail,)).await?; + let first: ClassInstanceRef = jvm.invoke_virtual(&iterator, "next", "()Ljava/lang/Object;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &first).await?, "a"); + + let iterator: ClassInstanceRef = jvm.invoke_virtual(&keys, "iterator", "()Ljava/util/Iterator;", ()).await?; + let tail = JavaLangString::from_rust_string(&jvm, "c").await?; + let value = JavaLangString::from_rust_string(&jvm, "C").await?; + let _: ClassInstanceRef = jvm + .invoke_virtual(&map, "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", (tail, value)) + .await?; + let first: ClassInstanceRef = jvm.invoke_virtual(&iterator, "next", "()Ljava/lang/Object;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &first).await?, "a"); + let iterator: ClassInstanceRef = jvm.invoke_virtual(&keys, "iterator", "()Ljava/util/Iterator;", ()).await?; let missing = JavaLangString::from_rust_string(&jvm, "missing").await?; let missing_value: ClassInstanceRef = jvm diff --git a/jvm/src/class_loader.rs b/jvm/src/class_loader.rs index aa739938..fdbaf561 100644 --- a/jvm/src/class_loader.rs +++ b/jvm/src/class_loader.rs @@ -38,14 +38,20 @@ struct ClassInitialization { pub struct Class { pub definition: Box, java_class: Arc>>>, + defining_loader: Option>, initialization: Arc, } impl Class { - pub fn new(definition: Box, java_class: Option>) -> Self { + pub fn new( + definition: Box, + java_class: Option>, + defining_loader: Option>, + ) -> Self { Self { definition, java_class: Arc::new(RwLock::new(java_class)), + defining_loader, initialization: Arc::new(ClassInitialization { state: Mutex::new(ClassInitializationState { status: InitState::NotInitialized, @@ -89,6 +95,20 @@ impl Class { pub fn java_class(&self) -> Box { self.java_class.read().clone().unwrap() } + + pub(crate) fn is_same_runtime_package(&self, other: &Self) -> bool { + let name = self.definition.name(); + let package = name.rsplit_once('/').map_or("", |(package, _)| package); + let other_name = other.definition.name(); + let other_package = other_name.rsplit_once('/').map_or("", |(package, _)| package); + + package == other_package + && match (&self.defining_loader, &other.defining_loader) { + (None, None) => true, + (Some(loader), Some(other_loader)) => loader.identity() == other_loader.identity(), + _ => false, + } + } } #[async_trait::async_trait] @@ -117,7 +137,7 @@ impl ClassLoaderWrapper for BootstrapClassLoaderWrapper<'_> { let definition = self.bootstrap_class_loader.load_class(jvm, name).await?; if let Some(definition) = definition { let java_class = JavaLangClass::from_rust_class(jvm, definition.clone(), None).await?; - let class = Class::new(definition, Some(java_class)); + let class = Class::new(definition, Some(java_class), None); jvm.register_class_internal(class.clone(), Some(self)).await?; Ok(Some(class)) @@ -144,7 +164,8 @@ impl ClassLoaderWrapper for JavaClassLoaderWrapper { if let Some(class) = class { let definition = JavaLangClass::to_rust_class(jvm, &class).await?; - Ok(Some(Class::new(definition, Some(class)))) + let defining_loader = JavaLangClass::class_loader(jvm, &class).await?; + Ok(Some(Class::new(definition, Some(class), defining_loader))) } else { Ok(None) } diff --git a/jvm/src/jvm.rs b/jvm/src/jvm.rs index 105bb4ca..35526ff1 100644 --- a/jvm/src/jvm.rs +++ b/jvm/src/jvm.rs @@ -82,7 +82,7 @@ impl Jvm { let bootstrap_classes = ["java/lang/Object", "java/lang/Runnable", "java/lang/Thread", "[B", "java/lang/Class"]; for class_name in bootstrap_classes.iter() { let class_definition = jvm.inner.bootstrap_class_loader.load_class(&jvm, class_name).await?.unwrap(); - let class = Class::new(class_definition, None); + let class = Class::new(class_definition, None, None); jvm.register_class_internal(class, None).await?; } @@ -340,28 +340,58 @@ impl Jvm { T: InvokeArg, U: From, { - let args = args.into_arg(); - tracing::trace!("Invoke virtual {}.{name}:{descriptor}({args:?})", instance.class_definition().name()); + let class_name = instance.class_definition().name(); + self.invoke_virtual_with_owner(instance, &class_name, name, descriptor, args).await + } - let class = instance.class_definition(); - let method = self.find_virtual_method(&*class, name, descriptor, false)?; - if let Some(x) = method { - let args = iter::once(JavaValue::Object(Some(clone_box(&**instance)))) - .chain(args.into_vec()) - .collect::>(); + pub async fn invoke_virtual_with_owner( + &self, + instance: &Box, + class_name: &str, + name: &str, + descriptor: &str, + args: T, + ) -> Result + where + T: InvokeArg, + U: From, + { + let args = args.into_arg(); + tracing::trace!("Invoke virtual {class_name}.{name}:{descriptor}({args:?})"); - let class = self.resolve_class(&class.name()).await?; // TODO we're resolving class twice - Ok(self - .execute_method(&class, Some(instance.clone()), &x, args.into_boxed_slice()) - .await? - .into()) - } else { - tracing::error!("No such method: {}.{name}:{descriptor}", class.name()); + let symbolic_class = self.resolve_class(class_name).await?; + let Some((resolved_class, resolved_method)) = self.resolve_method(&symbolic_class, name, descriptor) else { + tracing::error!("No such method: {class_name}.{name}:{descriptor}"); + return Err(self + .exception("java/lang/NoSuchMethodError", &format!("{class_name}.{name}:{descriptor}")) + .await); + }; + if resolved_method.access_flags().contains(MethodAccessFlags::STATIC) { + return Err(self + .exception("java/lang/IncompatibleClassChangeError", &format!("{class_name}.{name}:{descriptor}")) + .await); + } - Err(self - .exception("java/lang/NoSuchMethodError", &format!("{}.{}:{}", class.name(), name, descriptor)) - .await) + let runtime_class = self.resolve_class(&instance.class_definition().name()).await?; + let Some((declaring_class, method)) = self.select_virtual_method(&runtime_class, &resolved_class, &*resolved_method) else { + return Err(self + .exception("java/lang/AbstractMethodError", &format!("{class_name}.{name}:{descriptor}")) + .await); + }; + if method.access_flags().contains(MethodAccessFlags::ABSTRACT) { + return Err(self + .exception("java/lang/AbstractMethodError", &format!("{class_name}.{name}:{descriptor}")) + .await); } + + let args = iter::once(JavaValue::Object(Some(clone_box(&**instance)))) + .chain(args.into_vec()) + .collect::>(); + + Ok(self + .execute_method(&declaring_class, Some(instance.clone()), &method, args.into_boxed_slice()) + .await? + .into()) } // non-virtual @@ -824,7 +854,7 @@ impl Jvm { let java_class = Some(JavaLangClass::from_rust_class(self, class.clone(), class_loader.clone()).await?); - let class = Class::new(class, java_class.clone()); + let class = Class::new(class, java_class.clone(), class_loader.clone()); if let Some(x) = class_loader { self.register_class_internal(class, Some(&JavaClassLoaderWrapper::new(x))).await?; @@ -1197,19 +1227,57 @@ impl Jvm { } } - fn find_virtual_method(&self, class: &dyn ClassDefinition, name: &str, descriptor: &str, is_static: bool) -> Result>> { - let method = class.method(name, descriptor, false); + fn select_virtual_method(&self, runtime_class: &Class, resolved_class: &Class, resolved_method: &dyn Method) -> Option<(Class, Box)> { + let mut class = runtime_class.clone(); + loop { + if let Some(method) = class.definition.method(&resolved_method.name(), &resolved_method.descriptor(), false) + && (class.definition.name() == resolved_class.definition.name() + || self.method_overrides(&class, &*method, resolved_class, resolved_method)) + { + return Some((class, method)); + } + + class = self.get_class(&class.definition.super_class_name()?)?; + } + } + + fn method_overrides(&self, class: &Class, method: &dyn Method, overridden_class: &Class, overridden_method: &dyn Method) -> bool { + if method.access_flags().contains(MethodAccessFlags::PRIVATE) + || overridden_method + .access_flags() + .intersects(MethodAccessFlags::PRIVATE | MethodAccessFlags::STATIC | MethodAccessFlags::FINAL) + { + return false; + } + + if overridden_method + .access_flags() + .intersects(MethodAccessFlags::PUBLIC | MethodAccessFlags::PROTECTED) + || class.is_same_runtime_package(overridden_class) + { + return true; + } - if let Some(x) = method { - if x.access_flags().contains(MethodAccessFlags::STATIC) == is_static { - return Ok(Some(x)); + let mut super_class_name = class.definition.super_class_name(); + while let Some(name) = super_class_name { + let Some(super_class) = self.get_class(&name) else { + return false; + }; + if super_class.definition.name() == overridden_class.definition.name() { + break; } - } else if let Some(x) = class.super_class_name() { - let super_class = self.inner.classes.read().get(&x).unwrap().definition.clone(); - return self.find_virtual_method(&*super_class, name, descriptor, is_static); + + if let Some(super_method) = super_class.definition.method(&method.name(), &method.descriptor(), false) + && self.method_overrides(class, method, &super_class, &*super_method) + && self.method_overrides(&super_class, &*super_method, overridden_class, overridden_method) + { + return true; + } + + super_class_name = super_class.definition.super_class_name(); } - Ok(None) + false } async fn execute_method( diff --git a/jvm_rust/src/interpreter.rs b/jvm_rust/src/interpreter.rs index 43b822c6..d73b4555 100644 --- a/jvm_rust/src/interpreter.rs +++ b/jvm_rust/src/interpreter.rs @@ -683,7 +683,9 @@ impl Interpreter { .await); } - let result = jvm.invoke_virtual(&instance.unwrap(), &x.name, &x.descriptor, params).await?; + let result = jvm + .invoke_virtual_with_owner(&instance.unwrap(), &x.class, &x.name, &x.descriptor, params) + .await?; Self::push_invoke_result(stack_frame, result); } Opcode::Ior => { diff --git a/test_data/VirtualDispatch$HookCollisionMap.class b/test_data/VirtualDispatch$HookCollisionMap.class new file mode 100644 index 0000000000000000000000000000000000000000..d8e34289060ee6c0ab4f58b73d7c280ce0a8e058 GIT binary patch literal 407 zcmZ8dO-sW-5Pj36O`D`2YSoj_gP``%UObAIh_s+yl!7<6=|b18Ny#Q2{8xe^c<=}K zqr};W3SrsVH}B2te0+Yre*iefj*l7~4{nG$8id_@@hHYcuJrgqB`dj@2(!EtYeM5h zB`QB9IEP2G7Mf^z@IwR$34@u+@w968wsrqfu;mE zN*9vQf7@&w$S-VANw=!v;1>j<|;{Y@_)N<}Onf literal 0 HcmV?d00001 diff --git a/test_data/VirtualDispatch.class b/test_data/VirtualDispatch.class new file mode 100644 index 0000000000000000000000000000000000000000..dc82b6eb257bfd4e595771d14ddcfddae28c83c1 GIT binary patch literal 977 zcmZuve@_!Z5PjPMSB@*RS6Wc4f>vxPa{fTA7V$?bu}M)(O^xxdZM(w4^{%;IjeG<@ zj>eQAi66iZWt@dJw8h(GXKr@(&3m)+=kKrI0M_wJM*u+$V+O`C!4TQ!U$|v+XV-dL z+ZT18VPZo%(%)nVrZZIyA%*!!*NUReFAh+r=J6N}Uv3+m>zVI@`RZBaC1OQ3l49A2TqEIF)a8h)phC9;KMS z7|C1=g)m2mHHCP|z-26uvIB8g5d<1d_bzmNXL0;8-tO`CM(*Jn`H5(fYGb7JSpar)f=%6ee@ut(P3GY3x*%TRT za!KqG=}!Z%MGcV7Za!0G&?|1otBWnEl4nP7*A#ySqvSZkE7-i<7Sx)^=rCakd75zb z1;Esd)BcqHl4Kd#@s%U6@8mFS(yopvh)F!dbMjT{Hw0+`?@pk7Ji=rb`U%41&6Kld z#Ef)de#n{$GjZy4F@KCJ4BJ0&J^K^sE>_7~J;ptTE^>qZdD&$u2+Tl76eg_&#E_z~ fUqc)@Bv8a0b}&yGUQk{DFHszw%)WoTz5zJHk&O%t6Ilm2-n zv(>jyfMvpVumPuAr P;uf;7sa&Vl3=Tg5$O>4V literal 0 HcmV?d00001 diff --git a/test_data/dispatch/base/PackageMiddle.class b/test_data/dispatch/base/PackageMiddle.class new file mode 100644 index 0000000000000000000000000000000000000000..5f26cca824e104d6086b57ac4f4fbe725633abcf GIT binary patch literal 424 zcmZuuJ5K^Z5dMbO!FdXTsE8I8Bw*n+b{Gpytk960p93Zdhx)l%r$+?|S*bjM=05P`gAn^3t_ zvC>zBVt+8Hp^BOX+rbtbLgP_92{#mR;Cjz1EhElKGEHcY@`y^@ajIhN=~RknSj7(W zMauU>X`_K%3rz=9ZU@(I?`pu?C6`)kH^EzRy=IG9-2Cv-P9f0tJwlx`BAr>Kuq zEbp^uD$@rs4cSwgC&_|fdr6kgz3Y>x4D2E}y-F pUf3WUvNU0!$hY&C+RLFp?G;vY`wjK~bn_9+!D}_ZBeo0Z{s3MkSfl^| literal 0 HcmV?d00001 diff --git a/test_data/dispatch/foreign/ForeignChild.class b/test_data/dispatch/foreign/ForeignChild.class new file mode 100644 index 0000000000000000000000000000000000000000..78e5e25e954352f83b973349ac42755db198495e GIT binary patch literal 428 zcmZuuyG{Z@6g`*M!n(>!d?AGe30Sy|9ma-)3JnQ{eqhICWZ5ChVB%+Kp^1eb;71wn zfI$nVxX*L$Yd*i;KLA|f)I|zu2N@4pN}rv6wgr9TjpnF!^L3W7)ya#t!= z=9-XhwMPZyQE=dT*nvkVJ%~r)MmNv~nmd9IYb4>*bFK{ZmuPFY)?PmZEA2!dF6Sh;Re*-9$TC)HE literal 0 HcmV?d00001 diff --git a/test_data/src/VirtualDispatch.java b/test_data/src/VirtualDispatch.java new file mode 100644 index 00000000..2cdf28ad --- /dev/null +++ b/test_data/src/VirtualDispatch.java @@ -0,0 +1,25 @@ +import dispatch.foreign.ForeignChild; +import java.util.Iterator; +import java.util.LinkedHashMap; + +public class VirtualDispatch { + static class HookCollisionMap extends LinkedHashMap { + int collisionCalls; + + public void initializeMap() { + collisionCalls++; + } + } + + public static void main(String[] args) { + new ForeignChild().callHook(); + + HookCollisionMap map = new HookCollisionMap(); + map.put("a", "a"); + map.put("b", "b"); + Iterator iterator = map.keySet().iterator(); + System.out.println(map.collisionCalls); + System.out.println(iterator.next()); + System.out.println(iterator.next()); + } +} diff --git a/test_data/src/dispatch/base/PackageBase.java b/test_data/src/dispatch/base/PackageBase.java new file mode 100644 index 00000000..8013270a --- /dev/null +++ b/test_data/src/dispatch/base/PackageBase.java @@ -0,0 +1,11 @@ +package dispatch.base; + +public class PackageBase { + void hook() { + System.out.println("base"); + } + + public void callHook() { + hook(); + } +} diff --git a/test_data/src/dispatch/base/PackageMiddle.java b/test_data/src/dispatch/base/PackageMiddle.java new file mode 100644 index 00000000..dadb1d1b --- /dev/null +++ b/test_data/src/dispatch/base/PackageMiddle.java @@ -0,0 +1,7 @@ +package dispatch.base; + +public class PackageMiddle extends PackageBase { + void hook() { + System.out.println("middle"); + } +} diff --git a/test_data/src/dispatch/foreign/ForeignChild.java b/test_data/src/dispatch/foreign/ForeignChild.java new file mode 100644 index 00000000..470792bc --- /dev/null +++ b/test_data/src/dispatch/foreign/ForeignChild.java @@ -0,0 +1,9 @@ +package dispatch.foreign; + +import dispatch.base.PackageMiddle; + +public class ForeignChild extends PackageMiddle { + public void hook() { + System.out.println("foreign"); + } +} From bd46e1e7afc8ee141f9304843d704b8a3a51d0c6 Mon Sep 17 00:00:00 2001 From: Inseok Lee Date: Sun, 9 Aug 2026 18:35:21 +0900 Subject: [PATCH 3/3] Defer virtual dispatch rework --- .../src/classes/java/util/hash_map.rs | 21 +-- .../classes/java/util/hash_map_entry_set.rs | 3 +- .../src/classes/java/util/hash_map_key_set.rs | 3 +- .../src/classes/java/util/hash_map_values.rs | 7 +- .../src/classes/java/util/linked_hash_map.rs | 15 +-- jvm/src/class_loader.rs | 27 +--- jvm/src/jvm.rs | 126 ++++-------------- jvm_rust/src/interpreter.rs | 4 +- .../VirtualDispatch$HookCollisionMap.class | Bin 407 -> 0 bytes test_data/VirtualDispatch.class | Bin 977 -> 0 bytes test_data/VirtualDispatch.txt | 4 - test_data/dispatch/base/PackageBase.class | Bin 477 -> 0 bytes test_data/dispatch/base/PackageMiddle.class | Bin 424 -> 0 bytes test_data/dispatch/foreign/ForeignChild.class | Bin 428 -> 0 bytes test_data/src/VirtualDispatch.java | 25 ---- test_data/src/dispatch/base/PackageBase.java | 11 -- .../src/dispatch/base/PackageMiddle.java | 7 - .../src/dispatch/foreign/ForeignChild.java | 9 -- 18 files changed, 46 insertions(+), 216 deletions(-) delete mode 100644 test_data/VirtualDispatch$HookCollisionMap.class delete mode 100644 test_data/VirtualDispatch.class delete mode 100644 test_data/VirtualDispatch.txt delete mode 100644 test_data/dispatch/base/PackageBase.class delete mode 100644 test_data/dispatch/base/PackageMiddle.class delete mode 100644 test_data/dispatch/foreign/ForeignChild.class delete mode 100644 test_data/src/VirtualDispatch.java delete mode 100644 test_data/src/dispatch/base/PackageBase.java delete mode 100644 test_data/src/dispatch/base/PackageMiddle.java delete mode 100644 test_data/src/dispatch/foreign/ForeignChild.java diff --git a/java_runtime/src/classes/java/util/hash_map.rs b/java_runtime/src/classes/java/util/hash_map.rs index 31470047..e43d0271 100644 --- a/java_runtime/src/classes/java/util/hash_map.rs +++ b/java_runtime/src/classes/java/util/hash_map.rs @@ -163,9 +163,7 @@ impl HashMap { .await?; jvm.put_field(&mut this, "loadFactor", "F", load_factor).await?; jvm.put_field(&mut this, "modCount", "I", 0).await?; - let _: () = jvm - .invoke_virtual_with_owner(&this, "java/util/HashMap", "initializeMap", "()V", ()) - .await?; + let _: () = jvm.invoke_virtual(&this, "initializeMap", "()V", ()).await?; Ok(()) } @@ -270,9 +268,7 @@ impl HashMap { if Self::keys_equal(jvm, &key, &entry_key).await? { let old_value: ClassInstanceRef = jvm.get_field(&entry, "value", "Ljava/lang/Object;").await?; jvm.put_field(&mut entry, "value", "Ljava/lang/Object;", value).await?; - let _: () = jvm - .invoke_virtual_with_owner(&entry, "java/util/HashMap$Entry", "onAccess", "(Ljava/util/HashMap;)V", (this.clone(),)) - .await?; + let _: () = jvm.invoke_virtual(&entry, "onAccess", "(Ljava/util/HashMap;)V", (this.clone(),)).await?; return Ok(old_value); } } @@ -281,9 +277,8 @@ impl HashMap { } let _: () = jvm - .invoke_virtual_with_owner( + .invoke_virtual( &this, - "java/util/HashMap", "insertNewEntry", "(ILjava/lang/Object;Ljava/lang/Object;I)V", (key_hash, key, value, bucket_index as i32), @@ -331,9 +326,7 @@ impl HashMap { } let old_value: ClassInstanceRef = jvm.get_field(&entry, "value", "Ljava/lang/Object;").await?; - let _: () = jvm - .invoke_virtual_with_owner(&entry, "java/util/HashMap$Entry", "onRemoval", "(Ljava/util/HashMap;)V", (this.clone(),)) - .await?; + let _: () = jvm.invoke_virtual(&entry, "onRemoval", "(Ljava/util/HashMap;)V", (this.clone(),)).await?; let null_entry: ClassInstanceRef = None.into(); jvm.put_field(&mut entry, "next", "Ljava/util/HashMap$Entry;", null_entry).await?; @@ -410,9 +403,8 @@ impl HashMap { let mod_count: i32 = jvm.get_field(&this, "modCount", "I").await?; jvm.put_field(&mut this, "modCount", "I", mod_count.wrapping_add(1)).await?; let _: () = jvm - .invoke_virtual_with_owner( + .invoke_virtual( &this, - "java/util/HashMap", "storeNewEntry", "(ILjava/lang/Object;Ljava/lang/Object;I)V", (hash, key, value, bucket_index), @@ -492,9 +484,8 @@ impl HashMap { Self::ensure_table_for_insert(jvm, this).await?; let table: ClassInstanceRef> = jvm.get_field(this, "table", "[Ljava/util/HashMap$Entry;").await?; let bucket_index = Self::bucket_index(hash, jvm.array_length(&table).await?); - jvm.invoke_virtual_with_owner( + jvm.invoke_virtual( this, - "java/util/HashMap", "storeNewEntry", "(ILjava/lang/Object;Ljava/lang/Object;I)V", (hash, key, value, bucket_index as i32), diff --git a/java_runtime/src/classes/java/util/hash_map_entry_set.rs b/java_runtime/src/classes/java/util/hash_map_entry_set.rs index b1823826..4f51083e 100644 --- a/java_runtime/src/classes/java/util/hash_map_entry_set.rs +++ b/java_runtime/src/classes/java/util/hash_map_entry_set.rs @@ -127,8 +127,7 @@ impl HashMapEntrySet { let map: ClassInstanceRef = jvm.get_field(&this, "map", "Ljava/util/HashMap;").await?; - jvm.invoke_virtual_with_owner(&map, "java/util/HashMap", "entryIterator", "()Ljava/util/Iterator;", ()) - .await + jvm.invoke_virtual(&map, "entryIterator", "()Ljava/util/Iterator;", ()).await } async fn object_equals(jvm: &Jvm, left: &ClassInstanceRef, right: &ClassInstanceRef) -> Result { diff --git a/java_runtime/src/classes/java/util/hash_map_key_set.rs b/java_runtime/src/classes/java/util/hash_map_key_set.rs index 3061e999..cc083a2a 100644 --- a/java_runtime/src/classes/java/util/hash_map_key_set.rs +++ b/java_runtime/src/classes/java/util/hash_map_key_set.rs @@ -92,7 +92,6 @@ impl HashMapKeySet { let map: ClassInstanceRef = jvm.get_field(&this, "map", "Ljava/util/HashMap;").await?; - jvm.invoke_virtual_with_owner(&map, "java/util/HashMap", "keyIterator", "()Ljava/util/Iterator;", ()) - .await + jvm.invoke_virtual(&map, "keyIterator", "()Ljava/util/Iterator;", ()).await } } diff --git a/java_runtime/src/classes/java/util/hash_map_values.rs b/java_runtime/src/classes/java/util/hash_map_values.rs index 651eced0..323811d6 100644 --- a/java_runtime/src/classes/java/util/hash_map_values.rs +++ b/java_runtime/src/classes/java/util/hash_map_values.rs @@ -68,9 +68,7 @@ impl HashMapValues { tracing::debug!("java.util.HashMap$Values::remove({this:?}, {value:?})"); let map: ClassInstanceRef = jvm.get_field(&this, "map", "Ljava/util/HashMap;").await?; - let iterator: ClassInstanceRef = jvm - .invoke_virtual_with_owner(&map, "java/util/HashMap", "entryIterator", "()Ljava/util/Iterator;", ()) - .await?; + let iterator: ClassInstanceRef = jvm.invoke_virtual(&map, "entryIterator", "()Ljava/util/Iterator;", ()).await?; while jvm.invoke_virtual::<_, bool>(&iterator, "hasNext", "()Z", ()).await? { let entry: ClassInstanceRef = jvm.invoke_virtual(&iterator, "next", "()Ljava/lang/Object;", ()).await?; let entry_value: ClassInstanceRef = jvm.invoke_virtual(&entry, "getValue", "()Ljava/lang/Object;", ()).await?; @@ -106,7 +104,6 @@ impl HashMapValues { let map: ClassInstanceRef = jvm.get_field(&this, "map", "Ljava/util/HashMap;").await?; - jvm.invoke_virtual_with_owner(&map, "java/util/HashMap", "valueIterator", "()Ljava/util/Iterator;", ()) - .await + jvm.invoke_virtual(&map, "valueIterator", "()Ljava/util/Iterator;", ()).await } } diff --git a/java_runtime/src/classes/java/util/linked_hash_map.rs b/java_runtime/src/classes/java/util/linked_hash_map.rs index 71a3036a..92f7b1d3 100644 --- a/java_runtime/src/classes/java/util/linked_hash_map.rs +++ b/java_runtime/src/classes/java/util/linked_hash_map.rs @@ -191,9 +191,7 @@ impl LinkedHashMap { if entry.is_null() { return Ok(None.into()); } - let _: () = jvm - .invoke_virtual_with_owner(&entry, "java/util/HashMap$Entry", "onAccess", "(Ljava/util/HashMap;)V", (map,)) - .await?; + let _: () = jvm.invoke_virtual(&entry, "onAccess", "(Ljava/util/HashMap;)V", (map,)).await?; jvm.get_field(&entry, "value", "Ljava/lang/Object;").await } @@ -260,9 +258,8 @@ impl LinkedHashMap { let mod_count: i32 = jvm.get_field(&this, "modCount", "I").await?; jvm.put_field(&mut this, "modCount", "I", mod_count.wrapping_add(1)).await?; let _: () = jvm - .invoke_virtual_with_owner( + .invoke_virtual( &this, - "java/util/LinkedHashMap", "storeNewEntry", "(ILjava/lang/Object;Ljava/lang/Object;I)V", (hash, key, value, bucket_index), @@ -273,13 +270,7 @@ impl LinkedHashMap { let eldest: ClassInstanceRef = jvm.get_field(&header, "after", "Ljava/util/LinkedHashMap$Entry;").await?; let eldest_entry: ClassInstanceRef = ClassInstanceRef::new(eldest.instance.clone()); if jvm - .invoke_virtual_with_owner::<_, bool>( - &this, - "java/util/LinkedHashMap", - "removeEldestEntry", - "(Ljava/util/Map$Entry;)Z", - (eldest_entry,), - ) + .invoke_virtual::<_, bool>(&this, "removeEldestEntry", "(Ljava/util/Map$Entry;)Z", (eldest_entry,)) .await? { let key: ClassInstanceRef = jvm.get_field(&eldest, "key", "Ljava/lang/Object;").await?; diff --git a/jvm/src/class_loader.rs b/jvm/src/class_loader.rs index fdbaf561..aa739938 100644 --- a/jvm/src/class_loader.rs +++ b/jvm/src/class_loader.rs @@ -38,20 +38,14 @@ struct ClassInitialization { pub struct Class { pub definition: Box, java_class: Arc>>>, - defining_loader: Option>, initialization: Arc, } impl Class { - pub fn new( - definition: Box, - java_class: Option>, - defining_loader: Option>, - ) -> Self { + pub fn new(definition: Box, java_class: Option>) -> Self { Self { definition, java_class: Arc::new(RwLock::new(java_class)), - defining_loader, initialization: Arc::new(ClassInitialization { state: Mutex::new(ClassInitializationState { status: InitState::NotInitialized, @@ -95,20 +89,6 @@ impl Class { pub fn java_class(&self) -> Box { self.java_class.read().clone().unwrap() } - - pub(crate) fn is_same_runtime_package(&self, other: &Self) -> bool { - let name = self.definition.name(); - let package = name.rsplit_once('/').map_or("", |(package, _)| package); - let other_name = other.definition.name(); - let other_package = other_name.rsplit_once('/').map_or("", |(package, _)| package); - - package == other_package - && match (&self.defining_loader, &other.defining_loader) { - (None, None) => true, - (Some(loader), Some(other_loader)) => loader.identity() == other_loader.identity(), - _ => false, - } - } } #[async_trait::async_trait] @@ -137,7 +117,7 @@ impl ClassLoaderWrapper for BootstrapClassLoaderWrapper<'_> { let definition = self.bootstrap_class_loader.load_class(jvm, name).await?; if let Some(definition) = definition { let java_class = JavaLangClass::from_rust_class(jvm, definition.clone(), None).await?; - let class = Class::new(definition, Some(java_class), None); + let class = Class::new(definition, Some(java_class)); jvm.register_class_internal(class.clone(), Some(self)).await?; Ok(Some(class)) @@ -164,8 +144,7 @@ impl ClassLoaderWrapper for JavaClassLoaderWrapper { if let Some(class) = class { let definition = JavaLangClass::to_rust_class(jvm, &class).await?; - let defining_loader = JavaLangClass::class_loader(jvm, &class).await?; - Ok(Some(Class::new(definition, Some(class), defining_loader))) + Ok(Some(Class::new(definition, Some(class)))) } else { Ok(None) } diff --git a/jvm/src/jvm.rs b/jvm/src/jvm.rs index 35526ff1..105bb4ca 100644 --- a/jvm/src/jvm.rs +++ b/jvm/src/jvm.rs @@ -82,7 +82,7 @@ impl Jvm { let bootstrap_classes = ["java/lang/Object", "java/lang/Runnable", "java/lang/Thread", "[B", "java/lang/Class"]; for class_name in bootstrap_classes.iter() { let class_definition = jvm.inner.bootstrap_class_loader.load_class(&jvm, class_name).await?.unwrap(); - let class = Class::new(class_definition, None, None); + let class = Class::new(class_definition, None); jvm.register_class_internal(class, None).await?; } @@ -336,62 +336,32 @@ impl Jvm { } pub async fn invoke_virtual(&self, instance: &Box, name: &str, descriptor: &str, args: T) -> Result - where - T: InvokeArg, - U: From, - { - let class_name = instance.class_definition().name(); - self.invoke_virtual_with_owner(instance, &class_name, name, descriptor, args).await - } - - pub async fn invoke_virtual_with_owner( - &self, - instance: &Box, - class_name: &str, - name: &str, - descriptor: &str, - args: T, - ) -> Result where T: InvokeArg, U: From, { let args = args.into_arg(); - tracing::trace!("Invoke virtual {class_name}.{name}:{descriptor}({args:?})"); + tracing::trace!("Invoke virtual {}.{name}:{descriptor}({args:?})", instance.class_definition().name()); - let symbolic_class = self.resolve_class(class_name).await?; - let Some((resolved_class, resolved_method)) = self.resolve_method(&symbolic_class, name, descriptor) else { - tracing::error!("No such method: {class_name}.{name}:{descriptor}"); - return Err(self - .exception("java/lang/NoSuchMethodError", &format!("{class_name}.{name}:{descriptor}")) - .await); - }; - if resolved_method.access_flags().contains(MethodAccessFlags::STATIC) { - return Err(self - .exception("java/lang/IncompatibleClassChangeError", &format!("{class_name}.{name}:{descriptor}")) - .await); - } - - let runtime_class = self.resolve_class(&instance.class_definition().name()).await?; - let Some((declaring_class, method)) = self.select_virtual_method(&runtime_class, &resolved_class, &*resolved_method) else { - return Err(self - .exception("java/lang/AbstractMethodError", &format!("{class_name}.{name}:{descriptor}")) - .await); - }; - if method.access_flags().contains(MethodAccessFlags::ABSTRACT) { - return Err(self - .exception("java/lang/AbstractMethodError", &format!("{class_name}.{name}:{descriptor}")) - .await); - } + let class = instance.class_definition(); + let method = self.find_virtual_method(&*class, name, descriptor, false)?; + if let Some(x) = method { + let args = iter::once(JavaValue::Object(Some(clone_box(&**instance)))) + .chain(args.into_vec()) + .collect::>(); - let args = iter::once(JavaValue::Object(Some(clone_box(&**instance)))) - .chain(args.into_vec()) - .collect::>(); + let class = self.resolve_class(&class.name()).await?; // TODO we're resolving class twice + Ok(self + .execute_method(&class, Some(instance.clone()), &x, args.into_boxed_slice()) + .await? + .into()) + } else { + tracing::error!("No such method: {}.{name}:{descriptor}", class.name()); - Ok(self - .execute_method(&declaring_class, Some(instance.clone()), &method, args.into_boxed_slice()) - .await? - .into()) + Err(self + .exception("java/lang/NoSuchMethodError", &format!("{}.{}:{}", class.name(), name, descriptor)) + .await) + } } // non-virtual @@ -854,7 +824,7 @@ impl Jvm { let java_class = Some(JavaLangClass::from_rust_class(self, class.clone(), class_loader.clone()).await?); - let class = Class::new(class, java_class.clone(), class_loader.clone()); + let class = Class::new(class, java_class.clone()); if let Some(x) = class_loader { self.register_class_internal(class, Some(&JavaClassLoaderWrapper::new(x))).await?; @@ -1227,57 +1197,19 @@ impl Jvm { } } - fn select_virtual_method(&self, runtime_class: &Class, resolved_class: &Class, resolved_method: &dyn Method) -> Option<(Class, Box)> { - let mut class = runtime_class.clone(); - loop { - if let Some(method) = class.definition.method(&resolved_method.name(), &resolved_method.descriptor(), false) - && (class.definition.name() == resolved_class.definition.name() - || self.method_overrides(&class, &*method, resolved_class, resolved_method)) - { - return Some((class, method)); - } - - class = self.get_class(&class.definition.super_class_name()?)?; - } - } - - fn method_overrides(&self, class: &Class, method: &dyn Method, overridden_class: &Class, overridden_method: &dyn Method) -> bool { - if method.access_flags().contains(MethodAccessFlags::PRIVATE) - || overridden_method - .access_flags() - .intersects(MethodAccessFlags::PRIVATE | MethodAccessFlags::STATIC | MethodAccessFlags::FINAL) - { - return false; - } - - if overridden_method - .access_flags() - .intersects(MethodAccessFlags::PUBLIC | MethodAccessFlags::PROTECTED) - || class.is_same_runtime_package(overridden_class) - { - return true; - } + fn find_virtual_method(&self, class: &dyn ClassDefinition, name: &str, descriptor: &str, is_static: bool) -> Result>> { + let method = class.method(name, descriptor, false); - let mut super_class_name = class.definition.super_class_name(); - while let Some(name) = super_class_name { - let Some(super_class) = self.get_class(&name) else { - return false; - }; - if super_class.definition.name() == overridden_class.definition.name() { - break; + if let Some(x) = method { + if x.access_flags().contains(MethodAccessFlags::STATIC) == is_static { + return Ok(Some(x)); } - - if let Some(super_method) = super_class.definition.method(&method.name(), &method.descriptor(), false) - && self.method_overrides(class, method, &super_class, &*super_method) - && self.method_overrides(&super_class, &*super_method, overridden_class, overridden_method) - { - return true; - } - - super_class_name = super_class.definition.super_class_name(); + } else if let Some(x) = class.super_class_name() { + let super_class = self.inner.classes.read().get(&x).unwrap().definition.clone(); + return self.find_virtual_method(&*super_class, name, descriptor, is_static); } - false + Ok(None) } async fn execute_method( diff --git a/jvm_rust/src/interpreter.rs b/jvm_rust/src/interpreter.rs index d73b4555..43b822c6 100644 --- a/jvm_rust/src/interpreter.rs +++ b/jvm_rust/src/interpreter.rs @@ -683,9 +683,7 @@ impl Interpreter { .await); } - let result = jvm - .invoke_virtual_with_owner(&instance.unwrap(), &x.class, &x.name, &x.descriptor, params) - .await?; + let result = jvm.invoke_virtual(&instance.unwrap(), &x.name, &x.descriptor, params).await?; Self::push_invoke_result(stack_frame, result); } Opcode::Ior => { diff --git a/test_data/VirtualDispatch$HookCollisionMap.class b/test_data/VirtualDispatch$HookCollisionMap.class deleted file mode 100644 index d8e34289060ee6c0ab4f58b73d7c280ce0a8e058..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 407 zcmZ8dO-sW-5Pj36O`D`2YSoj_gP``%UObAIh_s+yl!7<6=|b18Ny#Q2{8xe^c<=}K zqr};W3SrsVH}B2te0+Yre*iefj*l7~4{nG$8id_@@hHYcuJrgqB`dj@2(!EtYeM5h zB`QB9IEP2G7Mf^z@IwR$34@u+@w968wsrqfu;mE zN*9vQf7@&w$S-VANw=!v;1>j<|;{Y@_)N<}Onf diff --git a/test_data/VirtualDispatch.class b/test_data/VirtualDispatch.class deleted file mode 100644 index dc82b6eb257bfd4e595771d14ddcfddae28c83c1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 977 zcmZuve@_!Z5PjPMSB@*RS6Wc4f>vxPa{fTA7V$?bu}M)(O^xxdZM(w4^{%;IjeG<@ zj>eQAi66iZWt@dJw8h(GXKr@(&3m)+=kKrI0M_wJM*u+$V+O`C!4TQ!U$|v+XV-dL z+ZT18VPZo%(%)nVrZZIyA%*!!*NUReFAh+r=J6N}Uv3+m>zVI@`RZBaC1OQ3l49A2TqEIF)a8h)phC9;KMS z7|C1=g)m2mHHCP|z-26uvIB8g5d<1d_bzmNXL0;8-tO`CM(*Jn`H5(fYGb7JSpar)f=%6ee@ut(P3GY3x*%TRT za!KqG=}!Z%MGcV7Za!0G&?|1otBWnEl4nP7*A#ySqvSZkE7-i<7Sx)^=rCakd75zb z1;Esd)BcqHl4Kd#@s%U6@8mFS(yopvh)F!dbMjT{Hw0+`?@pk7Ji=rb`U%41&6Kld z#Ef)de#n{$GjZy4F@KCJ4BJ0&J^K^sE>_7~J;ptTE^>qZdD&$u2+Tl76eg_&#E_z~ fUqc)@Bv8a0b}&yGUQk{DFHszw%)WoTz5zJHk&O%t6Ilm2-n zv(>jyfMvpVumPuAr P;uf;7sa&Vl3=Tg5$O>4V diff --git a/test_data/dispatch/base/PackageMiddle.class b/test_data/dispatch/base/PackageMiddle.class deleted file mode 100644 index 5f26cca824e104d6086b57ac4f4fbe725633abcf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 424 zcmZuuJ5K^Z5dMbO!FdXTsE8I8Bw*n+b{Gpytk960p93Zdhx)l%r$+?|S*bjM=05P`gAn^3t_ zvC>zBVt+8Hp^BOX+rbtbLgP_92{#mR;Cjz1EhElKGEHcY@`y^@ajIhN=~RknSj7(W zMauU>X`_K%3rz=9ZU@(I?`pu?C6`)kH^EzRy=IG9-2Cv-P9f0tJwlx`BAr>Kuq zEbp^uD$@rs4cSwgC&_|fdr6kgz3Y>x4D2E}y-F pUf3WUvNU0!$hY&C+RLFp?G;vY`wjK~bn_9+!D}_ZBeo0Z{s3MkSfl^| diff --git a/test_data/dispatch/foreign/ForeignChild.class b/test_data/dispatch/foreign/ForeignChild.class deleted file mode 100644 index 78e5e25e954352f83b973349ac42755db198495e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 428 zcmZuuyG{Z@6g`*M!n(>!d?AGe30Sy|9ma-)3JnQ{eqhICWZ5ChVB%+Kp^1eb;71wn zfI$nVxX*L$Yd*i;KLA|f)I|zu2N@4pN}rv6wgr9TjpnF!^L3W7)ya#t!= z=9-XhwMPZyQE=dT*nvkVJ%~r)MmNv~nmd9IYb4>*bFK{ZmuPFY)?PmZEA2!dF6Sh;Re*-9$TC)HE diff --git a/test_data/src/VirtualDispatch.java b/test_data/src/VirtualDispatch.java deleted file mode 100644 index 2cdf28ad..00000000 --- a/test_data/src/VirtualDispatch.java +++ /dev/null @@ -1,25 +0,0 @@ -import dispatch.foreign.ForeignChild; -import java.util.Iterator; -import java.util.LinkedHashMap; - -public class VirtualDispatch { - static class HookCollisionMap extends LinkedHashMap { - int collisionCalls; - - public void initializeMap() { - collisionCalls++; - } - } - - public static void main(String[] args) { - new ForeignChild().callHook(); - - HookCollisionMap map = new HookCollisionMap(); - map.put("a", "a"); - map.put("b", "b"); - Iterator iterator = map.keySet().iterator(); - System.out.println(map.collisionCalls); - System.out.println(iterator.next()); - System.out.println(iterator.next()); - } -} diff --git a/test_data/src/dispatch/base/PackageBase.java b/test_data/src/dispatch/base/PackageBase.java deleted file mode 100644 index 8013270a..00000000 --- a/test_data/src/dispatch/base/PackageBase.java +++ /dev/null @@ -1,11 +0,0 @@ -package dispatch.base; - -public class PackageBase { - void hook() { - System.out.println("base"); - } - - public void callHook() { - hook(); - } -} diff --git a/test_data/src/dispatch/base/PackageMiddle.java b/test_data/src/dispatch/base/PackageMiddle.java deleted file mode 100644 index dadb1d1b..00000000 --- a/test_data/src/dispatch/base/PackageMiddle.java +++ /dev/null @@ -1,7 +0,0 @@ -package dispatch.base; - -public class PackageMiddle extends PackageBase { - void hook() { - System.out.println("middle"); - } -} diff --git a/test_data/src/dispatch/foreign/ForeignChild.java b/test_data/src/dispatch/foreign/ForeignChild.java deleted file mode 100644 index 470792bc..00000000 --- a/test_data/src/dispatch/foreign/ForeignChild.java +++ /dev/null @@ -1,9 +0,0 @@ -package dispatch.foreign; - -import dispatch.base.PackageMiddle; - -public class ForeignChild extends PackageMiddle { - public void hook() { - System.out.println("foreign"); - } -}