From 9a4a05e72eb561d6ead68f6ff2c013c365e79c79 Mon Sep 17 00:00:00 2001 From: Inseok Lee Date: Sun, 5 Apr 2026 10:44:51 +0900 Subject: [PATCH 1/4] Store Throwable stack trace as Java String array instead of Rust pointer --- .../src/classes/java/lang/throwable.rs | 31 ++++++++++++------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/java_runtime/src/classes/java/lang/throwable.rs b/java_runtime/src/classes/java/lang/throwable.rs index d6463426..d00b0e74 100644 --- a/java_runtime/src/classes/java/lang/throwable.rs +++ b/java_runtime/src/classes/java/lang/throwable.rs @@ -1,7 +1,7 @@ -use alloc::{boxed::Box, format, string::String as RustString, sync::Arc, vec, vec::Vec}; +use alloc::{boxed::Box, format, vec, vec::Vec}; use java_class_proto::{JavaFieldProto, JavaMethodProto}; -use jvm::{ClassInstance, ClassInstanceRef, Jvm, Result, runtime::JavaLangString}; +use jvm::{Array, ClassInstance, ClassInstanceRef, Jvm, Result, runtime::JavaLangString}; use crate::{ RuntimeClassProto, RuntimeContext, @@ -46,7 +46,7 @@ impl Throwable { ], fields: vec![ JavaFieldProto::new("detailMessage", "Ljava/lang/String;", Default::default()), - JavaFieldProto::new("stackTrace", "[B", Default::default()), + JavaFieldProto::new("stackTrace", "[Ljava/lang/String;", Default::default()), ], access_flags: Default::default(), } @@ -77,9 +77,13 @@ impl Throwable { async fn fill_in_stack_trace(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef) -> Result> { tracing::debug!("java.lang.Throwable::fillInStackTrace({:?})", &this); - let stack_trace = Arc::new(jvm.stack_trace()); - - jvm.put_rust_object_field(&mut this, "stackTrace", stack_trace).await?; + let stack_trace = jvm.stack_trace(); + let mut stack_trace_array = jvm.instantiate_array("Ljava/lang/String;", stack_trace.len()).await?; + for (i, line) in stack_trace.iter().enumerate() { + let java_line = JavaLangString::from_rust_string(jvm, line).await?; + jvm.store_array(&mut stack_trace_array, i, core::iter::once(java_line)).await?; + } + jvm.put_field(&mut this, "stackTrace", "[Ljava/lang/String;", stack_trace_array).await?; Ok(this) } @@ -146,7 +150,7 @@ impl Throwable { } async fn do_print_stack_trace(jvm: &Jvm, this: ClassInstanceRef, stream_or_writer: Box) -> Result<()> { - let stack_trace: Arc> = jvm.get_rust_object_field(&this, "stackTrace").await?; + let stack_trace: ClassInstanceRef>> = jvm.get_field(&this, "stackTrace", "[Ljava/lang/String;").await?; // TODO we can call println(Ljava/lang/Object;)V let string: ClassInstanceRef = jvm.invoke_virtual(&this, "toString", "()Ljava/lang/String;", ()).await?; @@ -154,10 +158,15 @@ impl Throwable { .invoke_virtual(&stream_or_writer, "println", "(Ljava/lang/String;)V", (string,)) .await?; - for line in stack_trace.iter() { - let line = format!("\tat {line}"); - let line = JavaLangString::from_rust_string(jvm, &line).await?; - let _: () = jvm.invoke_virtual(&stream_or_writer, "println", "(Ljava/lang/String;)V", (line,)).await?; + if !stack_trace.is_null() { + let length = jvm.array_length(&stack_trace).await?; + let lines: Vec> = jvm.load_array(&stack_trace, 0, length).await?; + for line_ref in lines { + let line = JavaLangString::to_rust_string(jvm, &line_ref).await?; + let line = format!("\tat {line}"); + let line = JavaLangString::from_rust_string(jvm, &line).await?; + let _: () = jvm.invoke_virtual(&stream_or_writer, "println", "(Ljava/lang/String;)V", (line,)).await?; + } } Ok(()) From f089deaf5e77da5aabfb6f93bfaf5f8d166d88e2 Mon Sep 17 00:00:00 2001 From: Inseok Lee Date: Sun, 5 Apr 2026 10:45:08 +0900 Subject: [PATCH 2/4] Store java.lang.Class name as byte array and resolve ClassDefinition on demand --- java_runtime/src/classes/java/lang/class.rs | 4 +++- jvm/src/runtime/java_lang_class.rs | 25 ++++++++++++++++----- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/java_runtime/src/classes/java/lang/class.rs b/java_runtime/src/classes/java/lang/class.rs index a56a59a9..59911f3d 100644 --- a/java_runtime/src/classes/java/lang/class.rs +++ b/java_runtime/src/classes/java/lang/class.rs @@ -42,7 +42,9 @@ impl Class { ), ], fields: vec![ - JavaFieldProto::new("raw", "[B", Default::default()), // raw rust pointer of Box + // Stored as raw bytes instead of java/lang/String to avoid circular dependency: + // from_rust_class -> JavaLangString::from_rust_string -> new_class("java/lang/String") -> from_rust_class -> stack overflow + JavaFieldProto::new("nameBytes", "[B", Default::default()), JavaFieldProto::new("classLoader", "Ljava/lang/ClassLoader;", Default::default()), ], access_flags: Default::default(), diff --git a/jvm/src/runtime/java_lang_class.rs b/jvm/src/runtime/java_lang_class.rs index 6b75b2de..012f68c7 100644 --- a/jvm/src/runtime/java_lang_class.rs +++ b/jvm/src/runtime/java_lang_class.rs @@ -1,15 +1,23 @@ -use alloc::boxed::Box; +use alloc::{boxed::Box, string::String, vec::Vec}; -use crate::{Result, class_definition::ClassDefinition, class_instance::ClassInstance, jvm::Jvm}; +use bytemuck::cast_vec; + +use crate::{Array, ClassInstanceRef, Result, class_definition::ClassDefinition, class_instance::ClassInstance, jvm::Jvm}; pub struct JavaLangClass; impl JavaLangClass { #[allow(clippy::borrowed_box)] pub async fn to_rust_class(jvm: &Jvm, this: &Box) -> Result> { - let rust_class = jvm.get_rust_object_field(this, "raw").await?; - - Ok(rust_class) + let name_bytes: ClassInstanceRef> = jvm.get_field(this, "nameBytes", "[B").await?; + let len = jvm.array_length(&name_bytes).await?; + let name_bytes_vec: Vec = jvm.load_array(&name_bytes, 0, len).await?; + let class_name = String::from_utf8(cast_vec(name_bytes_vec)).unwrap_or_default(); + if let Some(class) = jvm.get_class(&class_name) { + Ok(class.definition) + } else { + Err(jvm.exception("java/lang/NoClassDefFoundError", &class_name).await) + } } pub async fn from_rust_class( @@ -19,7 +27,12 @@ impl JavaLangClass { ) -> Result> { let mut java_class = jvm.new_class("java/lang/Class", "()V", ()).await?; - jvm.put_rust_object_field(&mut java_class, "raw", rust_class).await?; + let class_name = rust_class.name(); + let mut name_bytes = jvm.instantiate_array("B", class_name.len()).await?; + let bytes: Vec = cast_vec(class_name.into_bytes()); + jvm.store_array(&mut name_bytes, 0, bytes).await?; + jvm.put_field(&mut java_class, "nameBytes", "[B", name_bytes).await?; + jvm.put_field(&mut java_class, "classLoader", "Ljava/lang/ClassLoader;", class_loader) .await?; From 8e83314ebc8848d37e14e39d37d03e77e6335f5f Mon Sep 17 00:00:00 2001 From: Inseok Lee Date: Sun, 5 Apr 2026 10:45:14 +0900 Subject: [PATCH 3/4] Store ZipFile raw bytes in Java array and reconstruct ZipArchive on demand --- .../src/classes/java/util/zip/zip_file.rs | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/java_runtime/src/classes/java/util/zip/zip_file.rs b/java_runtime/src/classes/java/util/zip/zip_file.rs index 073d8442..a62c387a 100644 --- a/java_runtime/src/classes/java/util/zip/zip_file.rs +++ b/java_runtime/src/classes/java/util/zip/zip_file.rs @@ -1,15 +1,14 @@ -use alloc::{string::ToString, sync::Arc, vec, vec::Vec}; +use alloc::{string::ToString, vec, vec::Vec}; use core::iter; // XXX for zip.. extern crate std; use std::io::{Cursor, Read}; -use parking_lot::Mutex; use zip::ZipArchive; use java_class_proto::{JavaFieldProto, JavaMethodProto}; -use jvm::{ClassInstanceRef, Jvm, Result, runtime::JavaLangString}; +use jvm::{Array, ClassInstanceRef, Jvm, Result, runtime::JavaLangString}; use crate::{ RuntimeClassProto, RuntimeContext, @@ -20,8 +19,6 @@ use crate::{ }, }; -type JavaZipArchive = Arc>>>>; - // class java.util.zip.ZipFile pub struct ZipFile; @@ -47,11 +44,19 @@ impl ZipFile { ), JavaMethodProto::new("entries", "()Ljava/util/Enumeration;", Self::entries, Default::default()), ], - fields: vec![JavaFieldProto::new("zip", "[B", Default::default())], + fields: vec![JavaFieldProto::new("zipData", "[B", Default::default())], access_flags: Default::default(), } } + async fn get_zip_archive(jvm: &Jvm, this: &ClassInstanceRef) -> Result>>> { + let zip_data: ClassInstanceRef> = jvm.get_field(this, "zipData", "[B").await?; + let length = jvm.array_length(&zip_data).await?; + let mut buf = vec![0u8; length]; + jvm.array_raw_buffer(&zip_data).await?.read(0, &mut buf).unwrap(); + Ok(ZipArchive::new(Cursor::new(buf)).unwrap()) + } + async fn init(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, file: ClassInstanceRef) -> Result<()> { tracing::debug!("java.util.zip.ZipFile::({:?}, {:?})", &this, &file,); @@ -63,10 +68,7 @@ impl ZipFile { let buf = jvm.instantiate_array("B", length as _).await?; let _: i32 = jvm.invoke_virtual(&is, "read", "([B)I", (buf.clone(),)).await?; - let mut rust_buf = vec![0; length as _]; - jvm.array_raw_buffer(&buf).await?.read(0, &mut rust_buf).unwrap(); - let zip = Arc::new(Mutex::new(ZipArchive::new(Cursor::new(rust_buf)).unwrap())); - jvm.put_rust_object_field(&mut this, "zip", zip).await?; + jvm.put_field(&mut this, "zipData", "[B", buf).await?; Ok(()) } @@ -82,8 +84,8 @@ impl ZipFile { let entry = jvm.new_class("java/util/zip/ZipEntry", "(Ljava/lang/String;)V", (name.clone(),)).await?; let name = JavaLangString::to_rust_string(jvm, &name).await?; - let zip: JavaZipArchive = jvm.get_rust_object_field(&this, "zip").await?; - let file_size = zip.lock().by_name(&name).map(|x| x.size()); + let mut zip = Self::get_zip_archive(jvm, &this).await?; + let file_size = zip.by_name(&name).map(|x| x.size()); if let Ok(x) = file_size { let _: () = jvm.invoke_virtual(&entry, "setSize", "(J)V", (x as i64,)).await?; @@ -97,8 +99,8 @@ impl ZipFile { async fn entries(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { tracing::debug!("java.util.zip.ZipFile::entries({:?})", &this); - let zip: JavaZipArchive = jvm.get_rust_object_field(&this, "zip").await?; - let names = zip.lock().file_names().map(|x| x.to_string()).collect::>(); + let zip = Self::get_zip_archive(jvm, &this).await?; + let names = zip.file_names().map(|x| x.to_string()).collect::>(); let mut name_array = jvm.instantiate_array("Ljava/lang/String;", names.len() as _).await?; for (i, name) in names.iter().enumerate() { @@ -129,9 +131,7 @@ impl ZipFile { let entry_name = JavaLangString::to_rust_string(jvm, &entry_name).await?; let data = { - let zip: JavaZipArchive = jvm.get_rust_object_field(&this, "zip").await?; - - let mut zip = zip.lock(); + let mut zip = Self::get_zip_archive(jvm, &this).await?; let mut file = zip.by_name(&entry_name).unwrap(); let mut buf = Vec::new(); From 973379ef07a6e4035ce0cd85adc10e0cd1d6b0bb Mon Sep 17 00:00:00 2001 From: Inseok Lee Date: Sun, 5 Apr 2026 10:45:20 +0900 Subject: [PATCH 4/4] Store InputStreamReader charset name and recreate Decoder per call --- .../classes/java/io/input_stream_reader.rs | 33 +++++++++---------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/java_runtime/src/classes/java/io/input_stream_reader.rs b/java_runtime/src/classes/java/io/input_stream_reader.rs index 67ba8d73..d1ba4b53 100644 --- a/java_runtime/src/classes/java/io/input_stream_reader.rs +++ b/java_runtime/src/classes/java/io/input_stream_reader.rs @@ -1,13 +1,12 @@ use core::cmp::min; -use alloc::{sync::Arc, vec}; +use alloc::vec; use bytemuck::{cast_slice, cast_vec}; -use encoding_rs::{Decoder, EUC_KR, UTF_8}; -use parking_lot::Mutex; +use encoding_rs::{EUC_KR, UTF_8}; use java_class_proto::{JavaFieldProto, JavaMethodProto}; -use jvm::{Array, ClassInstanceRef, JavaChar, Jvm, Result}; +use jvm::{Array, ClassInstanceRef, JavaChar, Jvm, Result, runtime::JavaLangString}; use crate::{ RuntimeClassProto, RuntimeContext, @@ -36,7 +35,7 @@ impl InputStreamReader { JavaFieldProto::new("readBufSize", "I", Default::default()), JavaFieldProto::new("writeBuf", "[C", Default::default()), JavaFieldProto::new("writeBufSize", "I", Default::default()), - JavaFieldProto::new("decoder", "[B", Default::default()), + JavaFieldProto::new("charset", "Ljava/lang/String;", Default::default()), ], access_flags: Default::default(), } @@ -48,16 +47,8 @@ impl InputStreamReader { let _: () = jvm.invoke_special(&this, "java/io/Reader", "", "()V", ()).await?; let charset = System::get_charset(jvm).await?; - - let decoder = if charset == "UTF-8" { - UTF_8.new_decoder() - } else if charset == "EUC-KR" { - EUC_KR.new_decoder() - } else { - unimplemented!("unsupported charset: {}", charset) - }; - - jvm.put_rust_object_field(&mut this, "decoder", Arc::new(Mutex::new(decoder))).await?; + let charset_java = JavaLangString::from_rust_string(jvm, &charset).await?; + jvm.put_field(&mut this, "charset", "Ljava/lang/String;", charset_java).await?; let read_buf = jvm.instantiate_array("B", BUF_SIZE).await?; jvm.put_field(&mut this, "readBuf", "[B", read_buf).await?; @@ -114,10 +105,18 @@ impl InputStreamReader { let mut read_buf_data = vec![0; read_buf_size as _]; jvm.array_raw_buffer(&read_buf).await?.read(0, &mut read_buf_data).unwrap(); - let decoder: Arc> = jvm.get_rust_object_field(&this, "decoder").await?; + let charset_ref = jvm.get_field(&this, "charset", "Ljava/lang/String;").await?; + let charset = JavaLangString::to_rust_string(jvm, &charset_ref).await?; + let mut decoder = if charset == "UTF-8" { + UTF_8.new_decoder_without_bom_handling() + } else if charset == "EUC-KR" { + EUC_KR.new_decoder_without_bom_handling() + } else { + unimplemented!("unsupported charset: {}", charset) + }; let mut decoded = vec![0; BUF_SIZE * 3]; - let (_, read, wrote, _) = decoder.lock().decode_to_utf16(&cast_vec(read_buf_data), &mut decoded, false); + let (_, read, wrote, _) = decoder.decode_to_utf16(&cast_vec(read_buf_data), &mut decoded, false); // advance readBuf let _: () = jvm