Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions java_runtime/src/classes/java/lang/string.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ impl String {
JavaMethodProto::new("startsWith", "(Ljava/lang/String;)Z", Self::starts_with, Default::default()),
JavaMethodProto::new("startsWith", "(Ljava/lang/String;I)Z", Self::starts_with_offset, Default::default()),
JavaMethodProto::new("endsWith", "(Ljava/lang/String;)Z", Self::ends_with, Default::default()),
JavaMethodProto::new("intern", "()Ljava/lang/String;", Self::intern, Default::default()),
],
fields: vec![JavaFieldProto::new("value", "[C", Default::default())],
access_flags: Default::default(),
Expand Down Expand Up @@ -741,6 +742,14 @@ impl String {
Ok(this_string.ends_with(&suffix_string))
}

async fn intern(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef<Self>) -> Result<ClassInstanceRef<Self>> {
tracing::debug!("java.lang.String::intern({:?})", &this);

let value = JavaLangString::to_rust_string(jvm, &this).await?;

Ok(jvm.intern_string(&value).await?.into())
Comment thread
dlunch marked this conversation as resolved.
Outdated
}
Comment thread
dlunch marked this conversation as resolved.

async fn value_of_boolean(jvm: &Jvm, _: &mut RuntimeContext, value: bool) -> Result<ClassInstanceRef<Self>> {
tracing::debug!("java.lang.String::valueOf({})", value);

Expand Down
5 changes: 5 additions & 0 deletions jvm/src/garbage_collector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ pub fn determine_garbage(
threads: &BTreeMap<u64, JvmThread>,
all_class_instances: &HashSet<Box<dyn ClassInstance>>,
classes: &BTreeMap<String, Class>,
interned_strings: &[Box<dyn ClassInstance>],
) -> Vec<Box<dyn ClassInstance>> {
let mut reachable_objects = HashSet::new();

Expand All @@ -25,6 +26,10 @@ pub fn determine_garbage(
find_reachable_objects(jvm, x, &mut reachable_objects);
});

interned_strings.iter().for_each(|x| {
find_reachable_objects(jvm, x, &mut reachable_objects);
});

all_class_instances.difference(&reachable_objects).cloned().collect()
}

Expand Down
21 changes: 20 additions & 1 deletion jvm/src/jvm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ struct JvmInner {
classes: RwLock<BTreeMap<String, Class>>,
threads: RwLock<BTreeMap<u64, JvmThread>>,
all_objects: RwLock<HashSet<Box<dyn ClassInstance>>>,
string_pool: RwLock<BTreeMap<String, Box<dyn ClassInstance>>>,
monitors: RwLock<BTreeMap<u64, Arc<Event>>>,
monitor_hasher: DefaultHashBuilder,
get_current_thread_id: Box<dyn Fn() -> u64 + Sync + Send>,
Expand All @@ -59,6 +60,7 @@ impl Jvm {
classes: RwLock::new(BTreeMap::new()),
threads: RwLock::new(BTreeMap::new()),
all_objects: RwLock::new(HashSet::new()),
string_pool: RwLock::new(BTreeMap::new()),
monitors: RwLock::new(BTreeMap::new()),
monitor_hasher: DefaultHashBuilder::default(),
get_current_thread_id: Box::new(get_current_thread_id),
Expand Down Expand Up @@ -475,6 +477,22 @@ impl Jvm {
Ok(())
}

// JVMS 5.1 string interning: equal string literals (and String.intern results) share one instance
pub async fn intern_string(&self, value: &str) -> Result<Box<dyn ClassInstance>> {
if let Some(interned) = self.inner.string_pool.read().get(value) {
return Ok(clone_box(&**interned));
}

let instance = JavaLangString::from_rust_string(self, value).await?;
self.inner.string_pool.write().insert(value.to_owned(), clone_box(&*instance));

Ok(instance)
}
Comment thread
dlunch marked this conversation as resolved.

pub(crate) fn interned_strings(&self) -> Vec<Box<dyn ClassInstance>> {
self.inner.string_pool.read().values().map(|x| clone_box(&**x)).collect()
}

pub fn has_class(&self, class_name: &str) -> bool {
self.inner.classes.read().contains_key(class_name)
}
Expand Down Expand Up @@ -652,8 +670,9 @@ impl Jvm {
let threads = self.inner.threads.read();
let all_objects = self.inner.all_objects.read();
let classes = self.inner.classes.read();
let interned_strings = self.interned_strings();

determine_garbage(self, &threads, &all_objects, &classes)
determine_garbage(self, &threads, &all_objects, &classes, &interned_strings)
};

let garbage_count = garbage.len();
Expand Down
21 changes: 21 additions & 0 deletions jvm/tests/test_string.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,24 @@ async fn test_to_rust_string_unpaired_surrogate() -> Result<()> {

Ok(())
}

#[tokio::test]
async fn test_intern_identity_survives_gc() -> Result<()> {
let jvm = test_jvm().await?;

let a = jvm.intern_string("interned").await?;
let b = jvm.intern_string("interned").await?;
assert!(a == b);

jvm.collect_garbage()?;

// the interned string is held by no frame, but the string pool roots it (and its [C),
// so a GC right after interning must collect nothing
let garbage_count = jvm.collect_garbage()?;
assert_eq!(garbage_count, 0);
Comment thread
Copilot marked this conversation as resolved.
Outdated

let c = jvm.intern_string("interned").await?;
assert!(a == c);

Ok(())
}
4 changes: 2 additions & 2 deletions jvm_rust/src/interpreter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use alloc::{boxed::Box, format, vec, vec::Vec};
use core::iter;

use classfile::{AttributeInfoCode, ConstantPoolReference, Opcode};
use jvm::{ClassInstance, JavaChar, JavaError, JavaType, JavaValue, Jvm, Result, runtime::JavaLangString};
use jvm::{ClassInstance, JavaChar, JavaError, JavaType, JavaValue, Jvm, Result};

use crate::stack_frame::StackFrame;

Expand Down Expand Up @@ -1036,7 +1036,7 @@ impl Interpreter {
ConstantPoolReference::Float(x) => JavaValue::Float(*x),
ConstantPoolReference::Long(x) => JavaValue::Long(*x),
ConstantPoolReference::Double(x) => JavaValue::Double(*x),
ConstantPoolReference::String(x) => JavaValue::Object(Some(JavaLangString::from_rust_string(jvm, x).await?)),
ConstantPoolReference::String(x) => JavaValue::Object(Some(jvm.intern_string(x).await?)),
Comment thread
dlunch marked this conversation as resolved.
ConstantPoolReference::Class(x) => JavaValue::Object(Some(jvm.resolve_class(x).await?.java_class())),
Comment thread
dlunch marked this conversation as resolved.
_ => unimplemented!(),
})
Expand Down
Binary file added test_data/StringIntern.class
Binary file not shown.
3 changes: 3 additions & 0 deletions test_data/StringIntern.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
true
false
true
11 changes: 11 additions & 0 deletions test_data/src/StringIntern.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
public class StringIntern {
public static void main(String[] args) {
String a = "x";
String b = "x";
System.out.println(a == b);

String c = new String("x");
System.out.println(c == a);
System.out.println(c.intern() == a);
}
}
Loading