Skip to content

Commit 10b83c8

Browse files
committed
Stabilize Object.hashCode and add regression tests
1 parent b23f25e commit 10b83c8

2 files changed

Lines changed: 61 additions & 3 deletions

File tree

java_runtime/src/classes/java/lang/object.rs

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1-
use core::{hash::BuildHasher, time::Duration};
1+
use core::{
2+
hash::{Hash, Hasher},
3+
time::Duration,
4+
};
25

36
use alloc::{boxed::Box, format, vec};
47

58
use dyn_clone::clone_box;
6-
use hashbrown::DefaultHashBuilder;
79
use java_class_proto::JavaMethodProto;
810
use java_constants::MethodAccessFlags;
911
use jvm::{ClassInstance, ClassInstanceRef, Jvm, Result, runtime::JavaLangString};
@@ -13,6 +15,27 @@ use crate::{Runtime, RuntimeClassProto, RuntimeContext, SpawnCallback, classes::
1315
// class java.lang.Object
1416
pub struct Object;
1517

18+
const FNV_OFFSET_BASIS_64: u64 = 0xcbf29ce484222325;
19+
const FNV_PRIME_64: u64 = 0x100000001b3;
20+
21+
#[derive(Default)]
22+
struct IdentityHasher(u64);
23+
24+
impl Hasher for IdentityHasher {
25+
fn finish(&self) -> u64 {
26+
self.0
27+
}
28+
29+
fn write(&mut self, bytes: &[u8]) {
30+
let mut hash = if self.0 == 0 { FNV_OFFSET_BASIS_64 } else { self.0 };
31+
for byte in bytes {
32+
hash ^= u64::from(*byte);
33+
hash = hash.wrapping_mul(FNV_PRIME_64);
34+
}
35+
self.0 = hash;
36+
}
37+
}
38+
1639
impl Object {
1740
pub fn as_proto() -> RuntimeClassProto {
1841
RuntimeClassProto {
@@ -61,7 +84,9 @@ impl Object {
6184

6285
let rust_this: Box<dyn ClassInstance> = this.into();
6386

64-
let hash = DefaultHashBuilder::default().hash_one(&rust_this);
87+
let mut hasher = IdentityHasher::default();
88+
rust_this.hash(&mut hasher);
89+
let hash = hasher.finish();
6590

6691
Ok(hash as _)
6792
}

java_runtime/tests/classes/java/lang/test_object.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,3 +123,36 @@ async fn test_clone_not_cloneable() -> Result<()> {
123123

124124
Ok(())
125125
}
126+
127+
#[tokio::test]
128+
async fn test_hash_code_is_stable_for_same_object() -> Result<()> {
129+
let runtime = TestRuntime::new(BTreeMap::new());
130+
let jvm = create_test_jvm(runtime).await?;
131+
132+
let object = jvm.new_class("java/lang/Object", "()V", ()).await?;
133+
134+
let first: i32 = jvm.invoke_virtual(&object, "hashCode", "()I", ()).await?;
135+
let second: i32 = jvm.invoke_virtual(&object, "hashCode", "()I", ()).await?;
136+
137+
assert_eq!(first, second);
138+
139+
Ok(())
140+
}
141+
142+
#[tokio::test]
143+
async fn test_hash_code_is_not_constant_across_objects() -> Result<()> {
144+
let runtime = TestRuntime::new(BTreeMap::new());
145+
let jvm = create_test_jvm(runtime).await?;
146+
147+
let mut hashes = hashbrown::HashSet::new();
148+
149+
for _ in 0..32 {
150+
let object = jvm.new_class("java/lang/Object", "()V", ()).await?;
151+
let hash: i32 = jvm.invoke_virtual(&object, "hashCode", "()I", ()).await?;
152+
hashes.insert(hash);
153+
}
154+
155+
assert!(hashes.len() > 1);
156+
157+
Ok(())
158+
}

0 commit comments

Comments
 (0)