From 3b620b869d31d47d25a2a3d13a4e756fa702afdd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Jul 2026 19:56:57 +0000 Subject: [PATCH 1/6] Bump bytemuck from 1.25.0 to 1.25.1 (#173) Bumps [bytemuck](https://github.com/Lokathor/bytemuck) from 1.25.0 to 1.25.1. - [Changelog](https://github.com/Lokathor/bytemuck/blob/main/changelog.md) - [Commits](https://github.com/Lokathor/bytemuck/compare/v1.25.0...v1.25.1) --- updated-dependencies: - dependency-name: bytemuck dependency-version: 1.25.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8fa03237..4ea10b81 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -65,9 +65,9 @@ checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" [[package]] name = "bytemuck" -version = "1.25.0" +version = "1.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" +checksum = "d6aedf8ae72766347502cf3cb4f41cf5e9cc37d28bee90f1fdaaae15f9cf9424" [[package]] name = "cfg-if" From fe5d11677048e9ae7524093e32a32552cdd5c39c Mon Sep 17 00:00:00 2001 From: Inseok Lee Date: Thu, 16 Jul 2026 14:24:40 +0900 Subject: [PATCH 2/6] Replace runtime panics with the matching Java exceptions (#174) * Replace runtime panics with the matching Java exceptions An unwrap audit found panics reachable from ordinary Java code: - File.length() returns 0 for a missing file; isDirectory/isFile lose their guard-then-unwrap shape - FileImpl (native runtime) maps open/read/write/seek failures to IOError instead of panicking, so FileInputStream and RandomAccessFile guards actually produce FileNotFoundException; FileOutputStream gains the same guard - File I/O operations (read/write/seek/available/length/setLength) throw java.io.IOException on failure via a shared helper - Class.forName resolves the class and throws ClassNotFoundException (new runtime class) instead of panicking on any not-yet-loaded name - StringBuffer.append(char)/append(char[]) keep exact UTF-16 units so unpaired surrogates no longer panic and pairs built char by char survive; String.valueOf(char) builds through [C for the same reason - PrintStream.println(char) replaces an unpaired surrogate with '?' like the JDK charset encoder - ZipFile validates the archive in its constructor and throws java.util.zip.ZipException (new runtime class) for a malformed archive; getInputStream returns null for a missing entry Expected outputs for the new fixtures are generated by a real JVM. Remaining unwraps are invariants (interpreter stack discipline, thread attach), guarded lookups, or documented gaps (lenient calendar normalization, ClassFormatError plumbing). * Inline the IOException conversion at each I/O call site --- java_runtime/src/classes/java/io/file.rs | 19 ++------ .../src/classes/java/io/file_input_stream.rs | 16 +++++-- .../src/classes/java/io/file_output_stream.rs | 18 ++++++-- .../classes/java/io/input_stream_reader.rs | 2 +- .../src/classes/java/io/print_stream.rs | 3 +- .../src/classes/java/io/random_access_file.rs | 27 ++++++++--- java_runtime/src/classes/java/lang.rs | 21 +++++---- java_runtime/src/classes/java/lang/class.rs | 6 ++- .../java/lang/class_not_found_exception.rs | 43 ++++++++++++++++++ java_runtime/src/classes/java/lang/string.rs | 6 ++- .../src/classes/java/lang/string_buffer.rs | 18 +++----- java_runtime/src/classes/java/util/zip.rs | 3 +- .../classes/java/util/zip/zip_exception.rs | 43 ++++++++++++++++++ .../src/classes/java/util/zip/zip_file.rs | 26 ++++++++--- java_runtime/src/loader.rs | 2 + java_runtime/src/runtime/io.rs | 1 + src/runtime.rs | 4 +- src/runtime/io.rs | 24 +++++----- test_data/FileErrors.class | Bin 0 -> 979 bytes test_data/FileErrors.txt | 3 ++ test_data/FileLength.class | Bin 0 -> 569 bytes test_data/FileLength.txt | 2 + test_data/ForName.class | Bin 0 -> 763 bytes test_data/ForName.txt | 2 + test_data/SurrogateChars.class | Bin 0 -> 900 bytes test_data/SurrogateChars.txt | 6 +++ test_data/ZipCorrupt.class | Bin 0 -> 707 bytes test_data/ZipCorrupt.txt | 1 + test_data/src/FileErrors.java | 28 ++++++++++++ test_data/src/FileLength.java | 9 ++++ test_data/src/ForName.java | 11 +++++ test_data/src/SurrogateChars.java | 20 ++++++++ test_data/src/ZipCorrupt.java | 14 ++++++ 33 files changed, 298 insertions(+), 80 deletions(-) create mode 100644 java_runtime/src/classes/java/lang/class_not_found_exception.rs create mode 100644 java_runtime/src/classes/java/util/zip/zip_exception.rs create mode 100644 test_data/FileErrors.class create mode 100644 test_data/FileErrors.txt create mode 100644 test_data/FileLength.class create mode 100644 test_data/FileLength.txt create mode 100644 test_data/ForName.class create mode 100644 test_data/ForName.txt create mode 100644 test_data/SurrogateChars.class create mode 100644 test_data/SurrogateChars.txt create mode 100644 test_data/ZipCorrupt.class create mode 100644 test_data/ZipCorrupt.txt create mode 100644 test_data/src/FileErrors.java create mode 100644 test_data/src/FileLength.java create mode 100644 test_data/src/ForName.java create mode 100644 test_data/src/SurrogateChars.java create mode 100644 test_data/src/ZipCorrupt.java diff --git a/java_runtime/src/classes/java/io/file.rs b/java_runtime/src/classes/java/io/file.rs index af938fc2..d3343d8a 100644 --- a/java_runtime/src/classes/java/io/file.rs +++ b/java_runtime/src/classes/java/io/file.rs @@ -59,12 +59,7 @@ impl File { let path = jvm.invoke_virtual(&this, "getPath", "()Ljava/lang/String;", ()).await?; let path = JavaLangString::to_rust_string(jvm, &path).await?; - let stat = context.metadata(&path).await; - if stat.is_err() { - return Ok(false); - } - - Ok(stat.unwrap().r#type == FileType::Directory) + Ok(context.metadata(&path).await.is_ok_and(|x| x.r#type == FileType::Directory)) } async fn is_file(jvm: &Jvm, context: &mut RuntimeContext, this: ClassInstanceRef) -> Result { @@ -73,12 +68,7 @@ impl File { let path = jvm.invoke_virtual(&this, "getPath", "()Ljava/lang/String;", ()).await?; let path = JavaLangString::to_rust_string(jvm, &path).await?; - let stat = context.metadata(&path).await; - if stat.is_err() { - return Ok(false); - } - - Ok(stat.unwrap().r#type == FileType::File) + Ok(context.metadata(&path).await.is_ok_and(|x| x.r#type == FileType::File)) } async fn delete(jvm: &Jvm, context: &mut RuntimeContext, this: ClassInstanceRef) -> Result { @@ -96,8 +86,7 @@ impl File { let path = jvm.invoke_virtual(&this, "getPath", "()Ljava/lang/String;", ()).await?; let path = JavaLangString::to_rust_string(jvm, &path).await?; - let stat = context.metadata(&path).await.unwrap(); - - Ok(stat.size as _) + // File.length() is 0 when the file does not exist + Ok(context.metadata(&path).await.map_or(0, |x| x.size as _)) } } diff --git a/java_runtime/src/classes/java/io/file_input_stream.rs b/java_runtime/src/classes/java/io/file_input_stream.rs index 9a111c88..8b923f07 100644 --- a/java_runtime/src/classes/java/io/file_input_stream.rs +++ b/java_runtime/src/classes/java/io/file_input_stream.rs @@ -79,8 +79,12 @@ impl FileInputStream { let rust_file = FileDescriptor::file(jvm, context, fd).await?; // TODO get os buffer size - let stat = rust_file.metadata().await.unwrap(); - let tell = rust_file.tell().await.unwrap(); + let Ok(stat) = rust_file.metadata().await else { + return Err(jvm.exception("java/io/IOException", "I/O error").await); + }; + let Ok(tell) = rust_file.tell().await else { + return Err(jvm.exception("java/io/IOException", "I/O error").await); + }; let available = stat.size - tell; @@ -101,7 +105,9 @@ impl FileInputStream { let mut rust_file = FileDescriptor::file(jvm, context, fd).await?; let mut rust_buf = vec![0; length as _]; - let read = rust_file.read(&mut rust_buf).await.unwrap(); + let Ok(read) = rust_file.read(&mut rust_buf).await else { + return Err(jvm.exception("java/io/IOException", "I/O error").await); + }; if read == 0 { return Ok(-1); } @@ -118,7 +124,9 @@ impl FileInputStream { let mut rust_file = FileDescriptor::file(jvm, context, fd).await?; let mut buf = [0; 1]; - let read = rust_file.read(&mut buf).await.unwrap(); + let Ok(read) = rust_file.read(&mut buf).await else { + return Err(jvm.exception("java/io/IOException", "I/O error").await); + }; if read == 0 { return Ok(-1); } diff --git a/java_runtime/src/classes/java/io/file_output_stream.rs b/java_runtime/src/classes/java/io/file_output_stream.rs index 89045226..02b5fcc3 100644 --- a/java_runtime/src/classes/java/io/file_output_stream.rs +++ b/java_runtime/src/classes/java/io/file_output_stream.rs @@ -42,8 +42,12 @@ impl FileOutputStream { let path = jvm.invoke_virtual(&file, "getPath", "()Ljava/lang/String;", ()).await?; let path = JavaLangString::to_rust_string(jvm, &path).await?; - let fd = context.open(&path, true).await.unwrap(); - let fd = FileDescriptor::from_fd(jvm, fd).await?; + let fd = context.open(&path, true).await; + if fd.is_err() { + return Err(jvm.exception("java/io/FileNotFoundException", "File not found").await); + } + + let fd = FileDescriptor::from_fd(jvm, fd.unwrap()).await?; let _: () = jvm .invoke_special(&this, "java/io/FileOutputStream", "", "(Ljava/io/FileDescriptor;)V", (fd,)) @@ -81,9 +85,11 @@ impl FileOutputStream { let mut file = FileDescriptor::file(jvm, context, fd).await?; let mut buf = vec![0; length as _]; - jvm.array_raw_buffer(&buffer).await?.read(offset as _, &mut buf).unwrap(); + jvm.array_raw_buffer(&buffer).await?.read(offset as _, &mut buf)?; - file.write(cast_slice(&buf)).await.unwrap(); + if file.write(cast_slice(&buf)).await.is_err() { + return Err(jvm.exception("java/io/IOException", "I/O error").await); + } Ok(()) } @@ -94,7 +100,9 @@ impl FileOutputStream { let fd = jvm.get_field(&this, "fd", "Ljava/io/FileDescriptor;").await?; let mut file = FileDescriptor::file(jvm, context, fd).await?; - file.write(&[byte as u8]).await.unwrap(); + if file.write(&[byte as u8]).await.is_err() { + return Err(jvm.exception("java/io/IOException", "I/O error").await); + } Ok(()) } 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 09c9763a..d210b9f1 100644 --- a/java_runtime/src/classes/java/io/input_stream_reader.rs +++ b/java_runtime/src/classes/java/io/input_stream_reader.rs @@ -103,7 +103,7 @@ impl InputStreamReader { let read_buf_size: i32 = jvm.get_field(&this, "readBufSize", "I").await?; 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(); + jvm.array_raw_buffer(&read_buf).await?.read(0, &mut read_buf_data)?; let charset_ref = jvm.get_field(&this, "charset", "Ljava/lang/String;").await?; let charset = JavaLangString::to_rust_string(jvm, &charset_ref).await?; diff --git a/java_runtime/src/classes/java/io/print_stream.rs b/java_runtime/src/classes/java/io/print_stream.rs index f611cbbb..aa0b5a29 100644 --- a/java_runtime/src/classes/java/io/print_stream.rs +++ b/java_runtime/src/classes/java/io/print_stream.rs @@ -110,7 +110,8 @@ impl PrintStream { async fn println_char(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, char: JavaChar) -> Result<()> { tracing::debug!("java.io.PrintStream::println({this:?}, {char:?})"); - let char = char::from_u32(char as _).unwrap(); + // an unpaired surrogate is not a valid char; the JDK charset encoder replaces it with '?' + let char = char::from_u32(char as _).unwrap_or('?'); let java_string = JavaLangString::from_rust_string(jvm, &char.to_string()).await?; diff --git a/java_runtime/src/classes/java/io/random_access_file.rs b/java_runtime/src/classes/java/io/random_access_file.rs index 62b0291d..2defa325 100644 --- a/java_runtime/src/classes/java/io/random_access_file.rs +++ b/java_runtime/src/classes/java/io/random_access_file.rs @@ -114,7 +114,9 @@ impl RandomAccessFile { let mut rust_file = FileDescriptor::file(jvm, context, fd).await?; let mut rust_buf = vec![0; length as usize]; - let read = rust_file.read(&mut rust_buf).await.unwrap(); + let Ok(read) = rust_file.read(&mut rust_buf).await else { + return Err(jvm.exception("java/io/IOException", "I/O error").await); + }; jvm.array_raw_buffer_mut(&mut buf).await?.write(offset as _, &rust_buf)?; @@ -144,8 +146,10 @@ impl RandomAccessFile { let mut rust_file = FileDescriptor::file(jvm, context, fd).await?; let mut rust_buf = vec![0; length as usize]; - jvm.array_raw_buffer(&buf).await?.read(offset as _, &mut rust_buf).unwrap(); - rust_file.write(&cast_vec(rust_buf)).await.unwrap(); + jvm.array_raw_buffer(&buf).await?.read(offset as _, &mut rust_buf)?; + if rust_file.write(&cast_vec(rust_buf)).await.is_err() { + return Err(jvm.exception("java/io/IOException", "I/O error").await); + } Ok(()) } @@ -156,7 +160,9 @@ impl RandomAccessFile { let fd = jvm.get_field(&this, "fd", "Ljava/io/FileDescriptor;").await?; let mut rust_file = FileDescriptor::file(jvm, context, fd).await?; - rust_file.seek(pos as _).await.unwrap(); + if rust_file.seek(pos as _).await.is_err() { + return Err(jvm.exception("java/io/IOException", "I/O error").await); + } Ok(()) } @@ -167,7 +173,9 @@ impl RandomAccessFile { let fd = jvm.get_field(&this, "fd", "Ljava/io/FileDescriptor;").await?; let mut rust_file = FileDescriptor::file(jvm, context, fd).await?; - rust_file.set_len(new_length as _).await.unwrap(); + if rust_file.set_len(new_length as _).await.is_err() { + return Err(jvm.exception("java/io/IOException", "I/O error").await); + } Ok(()) } @@ -178,7 +186,10 @@ impl RandomAccessFile { let fd = jvm.get_field(&this, "fd", "Ljava/io/FileDescriptor;").await?; let rust_file = FileDescriptor::file(jvm, context, fd).await?; - let len = rust_file.metadata().await.unwrap().size; + let Ok(metadata) = rust_file.metadata().await else { + return Err(jvm.exception("java/io/IOException", "I/O error").await); + }; + let len = metadata.size; Ok(len as i64) } @@ -189,7 +200,9 @@ impl RandomAccessFile { let fd = jvm.get_field(&this, "fd", "Ljava/io/FileDescriptor;").await?; let rust_file = FileDescriptor::file(jvm, context, fd).await?; - let pos = rust_file.tell().await.unwrap(); + let Ok(pos) = rust_file.tell().await else { + return Err(jvm.exception("java/io/IOException", "I/O error").await); + }; Ok(pos as i64) } diff --git a/java_runtime/src/classes/java/lang.rs b/java_runtime/src/classes/java/lang.rs index 67ae16d2..416ca554 100644 --- a/java_runtime/src/classes/java/lang.rs +++ b/java_runtime/src/classes/java/lang.rs @@ -5,6 +5,7 @@ mod array_store_exception; mod class; mod class_cast_exception; mod class_loader; +mod class_not_found_exception; mod clone_not_supported_exception; mod cloneable; mod comparable; @@ -41,14 +42,14 @@ mod unsupported_operation_exception; pub use self::{ abstract_method_error::AbstractMethodError, arithmetic_exception::ArithmeticException, array_index_out_of_bounds_exception::ArrayIndexOutOfBoundsException, array_store_exception::ArrayStoreException, class::Class, - class_cast_exception::ClassCastException, class_loader::ClassLoader, clone_not_supported_exception::CloneNotSupportedException, - cloneable::Cloneable, comparable::Comparable, error::Error, exception::Exception, exception_in_initializer_error::ExceptionInInitializerError, - illegal_argument_exception::IllegalArgumentException, incompatible_class_change_error::IncompatibleClassChangeError, - index_out_of_bounds_exception::IndexOutOfBoundsException, instantiation_error::InstantiationError, integer::Integer, - interrupted_exception::InterruptedException, linkage_error::LinkageError, math::Math, negative_array_size_exception::NegativeArraySizeException, - no_class_def_found_error::NoClassDefFoundError, no_such_field_error::NoSuchFieldError, no_such_method_error::NoSuchMethodError, - null_pointer_exception::NullPointerException, number_format_exception::NumberFormatException, object::Object, runnable::Runnable, - runtime::Runtime, runtime_exception::RuntimeException, security_exception::SecurityException, string::String, string_buffer::StringBuffer, - string_index_out_of_bounds_exception::StringIndexOutOfBoundsException, system::System, thread::Thread, throwable::Throwable, - unsupported_operation_exception::UnsupportedOperationException, + class_cast_exception::ClassCastException, class_loader::ClassLoader, class_not_found_exception::ClassNotFoundException, + clone_not_supported_exception::CloneNotSupportedException, cloneable::Cloneable, comparable::Comparable, error::Error, exception::Exception, + exception_in_initializer_error::ExceptionInInitializerError, illegal_argument_exception::IllegalArgumentException, + incompatible_class_change_error::IncompatibleClassChangeError, index_out_of_bounds_exception::IndexOutOfBoundsException, + instantiation_error::InstantiationError, integer::Integer, interrupted_exception::InterruptedException, linkage_error::LinkageError, math::Math, + negative_array_size_exception::NegativeArraySizeException, no_class_def_found_error::NoClassDefFoundError, no_such_field_error::NoSuchFieldError, + no_such_method_error::NoSuchMethodError, null_pointer_exception::NullPointerException, number_format_exception::NumberFormatException, + object::Object, runnable::Runnable, runtime::Runtime, runtime_exception::RuntimeException, security_exception::SecurityException, string::String, + string_buffer::StringBuffer, string_index_out_of_bounds_exception::StringIndexOutOfBoundsException, system::System, thread::Thread, + throwable::Throwable, unsupported_operation_exception::UnsupportedOperationException, }; diff --git a/java_runtime/src/classes/java/lang/class.rs b/java_runtime/src/classes/java/lang/class.rs index a8c8e682..a2f62d50 100644 --- a/java_runtime/src/classes/java/lang/class.rs +++ b/java_runtime/src/classes/java/lang/class.rs @@ -103,8 +103,10 @@ impl Class { let rust_name = JavaLangString::to_rust_string(jvm, &name).await?; let qualified_name = rust_name.replace('.', "/"); - let class = jvm.get_class(&qualified_name).unwrap().java_class(); - Ok(class.into()) + match jvm.resolve_class(&qualified_name).await { + Ok(class) => Ok(class.java_class().into()), + Err(_) => Err(jvm.exception("java/lang/ClassNotFoundException", &rust_name).await), + } } } diff --git a/java_runtime/src/classes/java/lang/class_not_found_exception.rs b/java_runtime/src/classes/java/lang/class_not_found_exception.rs new file mode 100644 index 00000000..f4f48d30 --- /dev/null +++ b/java_runtime/src/classes/java/lang/class_not_found_exception.rs @@ -0,0 +1,43 @@ +use alloc::vec; + +use java_class_proto::JavaMethodProto; +use jvm::{ClassInstanceRef, Jvm, Result}; + +use crate::{RuntimeClassProto, RuntimeContext, classes::java::lang::String}; + +// class java.lang.ClassNotFoundException +pub struct ClassNotFoundException; + +impl ClassNotFoundException { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/lang/ClassNotFoundException", + parent_class: Some("java/lang/Exception"), + interfaces: vec![], + methods: vec![ + JavaMethodProto::new("", "()V", Self::init, Default::default()), + JavaMethodProto::new("", "(Ljava/lang/String;)V", Self::init_with_message, Default::default()), + ], + fields: vec![], + access_flags: Default::default(), + } + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result<()> { + tracing::debug!("java.lang.ClassNotFoundException::({this:?})"); + + let _: () = jvm.invoke_special(&this, "java/lang/Exception", "", "()V", ()).await?; + + Ok(()) + } + + async fn init_with_message(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, message: ClassInstanceRef) -> Result<()> { + tracing::debug!("java.lang.ClassNotFoundException::({this:?}, {message:?})"); + + let _: () = jvm + .invoke_special(&this, "java/lang/Exception", "", "(Ljava/lang/String;)V", (message,)) + .await?; + + Ok(()) + } +} diff --git a/java_runtime/src/classes/java/lang/string.rs b/java_runtime/src/classes/java/lang/string.rs index 18fb9d0f..6c51f0cd 100644 --- a/java_runtime/src/classes/java/lang/string.rs +++ b/java_runtime/src/classes/java/lang/string.rs @@ -379,9 +379,11 @@ impl String { async fn value_of_char(jvm: &Jvm, _: &mut RuntimeContext, value: JavaChar) -> Result> { tracing::debug!("java.lang.String::valueOf({value})"); - let string = RustString::from_utf16(&[value]).unwrap(); + // build through [C so an unpaired surrogate is preserved + let mut chars = jvm.instantiate_array("C", 1).await?; + jvm.store_array(&mut chars, 0, [value]).await?; - Ok(JavaLangString::from_rust_string(jvm, &string).await?.into()) + Ok(jvm.new_class("java/lang/String", "([C)V", (chars,)).await?.into()) } async fn value_of_integer(jvm: &Jvm, _: &mut RuntimeContext, value: i32) -> Result> { diff --git a/java_runtime/src/classes/java/lang/string_buffer.rs b/java_runtime/src/classes/java/lang/string_buffer.rs index 960f3a2c..23efd99d 100644 --- a/java_runtime/src/classes/java/lang/string_buffer.rs +++ b/java_runtime/src/classes/java/lang/string_buffer.rs @@ -1,8 +1,4 @@ -use alloc::{ - string::{String as RustString, ToString}, - vec, - vec::Vec, -}; +use alloc::{string::ToString, vec, vec::Vec}; use java_class_proto::{JavaFieldProto, JavaMethodProto}; use jvm::{Array, ClassInstanceRef, JavaChar, Jvm, Result, runtime::JavaLangString}; @@ -162,9 +158,7 @@ impl StringBuffer { async fn append_character(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, value: u16) -> Result> { tracing::debug!("java.lang.StringBuffer::append({this:?}, {value:?})"); - let value = RustString::from_utf16(&[value]).unwrap(); - - Self::append(jvm, &mut this, &value).await?; + Self::append_utf16(jvm, &mut this, vec![value]).await?; Ok(this) } @@ -180,9 +174,8 @@ impl StringBuffer { tracing::debug!("java.lang.StringBuffer::append({this:?}, {array:?}, {offset:?}, {length:?})"); let value: Vec = jvm.load_array(&array, offset as _, length as _).await?; - let string = RustString::from_utf16(&value).unwrap(); - Self::append(jvm, &mut this, &string).await?; + Self::append_utf16(jvm, &mut this, value).await?; Ok(this) } @@ -237,9 +230,12 @@ impl StringBuffer { } async fn append(jvm: &Jvm, this: &mut ClassInstanceRef, string: &str) -> Result<()> { + Self::append_utf16(jvm, this, string.encode_utf16().collect()).await + } + + async fn append_utf16(jvm: &Jvm, this: &mut ClassInstanceRef, value_to_add: Vec) -> Result<()> { let current_count: i32 = jvm.get_field(this, "count", "I").await?; - let value_to_add = string.encode_utf16().collect::>(); let count_to_add = value_to_add.len() as i32; StringBuffer::ensure_capacity(jvm, this, (current_count + count_to_add) as _).await?; diff --git a/java_runtime/src/classes/java/util/zip.rs b/java_runtime/src/classes/java/util/zip.rs index 1b44f678..0f442a04 100644 --- a/java_runtime/src/classes/java/util/zip.rs +++ b/java_runtime/src/classes/java/util/zip.rs @@ -1,5 +1,6 @@ mod zip_entry; +mod zip_exception; mod zip_file; mod zip_file_entries; -pub use {zip_entry::ZipEntry, zip_file::ZipFile, zip_file_entries::ZipFileEntries}; +pub use {zip_entry::ZipEntry, zip_exception::ZipException, zip_file::ZipFile, zip_file_entries::ZipFileEntries}; diff --git a/java_runtime/src/classes/java/util/zip/zip_exception.rs b/java_runtime/src/classes/java/util/zip/zip_exception.rs new file mode 100644 index 00000000..fffde9f5 --- /dev/null +++ b/java_runtime/src/classes/java/util/zip/zip_exception.rs @@ -0,0 +1,43 @@ +use alloc::vec; + +use java_class_proto::JavaMethodProto; +use jvm::{ClassInstanceRef, Jvm, Result}; + +use crate::{RuntimeClassProto, RuntimeContext, classes::java::lang::String}; + +// class java.util.zip.ZipException +pub struct ZipException; + +impl ZipException { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/util/zip/ZipException", + parent_class: Some("java/io/IOException"), + interfaces: vec![], + methods: vec![ + JavaMethodProto::new("", "()V", Self::init, Default::default()), + JavaMethodProto::new("", "(Ljava/lang/String;)V", Self::init_with_message, Default::default()), + ], + fields: vec![], + access_flags: Default::default(), + } + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result<()> { + tracing::debug!("java.util.zip.ZipException::({this:?})"); + + let _: () = jvm.invoke_special(&this, "java/io/IOException", "", "()V", ()).await?; + + Ok(()) + } + + async fn init_with_message(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, message: ClassInstanceRef) -> Result<()> { + tracing::debug!("java.util.zip.ZipException::({this:?}, {message:?})"); + + let _: () = jvm + .invoke_special(&this, "java/io/IOException", "", "(Ljava/lang/String;)V", (message,)) + .await?; + + Ok(()) + } +} 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 36afd213..23498a4d 100644 --- a/java_runtime/src/classes/java/util/zip/zip_file.rs +++ b/java_runtime/src/classes/java/util/zip/zip_file.rs @@ -53,8 +53,12 @@ impl ZipFile { 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()) + jvm.array_raw_buffer(&zip_data).await?.read(0, &mut buf)?; + + match ZipArchive::new(Cursor::new(buf)) { + Ok(x) => Ok(x), + Err(err) => Err(jvm.exception("java/util/zip/ZipException", &err.to_string()).await), + } } async fn init(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, file: ClassInstanceRef) -> Result<()> { @@ -70,6 +74,9 @@ impl ZipFile { jvm.put_field(&mut this, "zipData", "[B", buf).await?; + // the constructor throws ZipException for a malformed archive + let _ = Self::get_zip_archive(jvm, &this).await?; + Ok(()) } @@ -132,17 +139,22 @@ impl ZipFile { let data = { let mut zip = Self::get_zip_archive(jvm, &this).await?; - let mut file = zip.by_name(&entry_name).unwrap(); + let file = zip.by_name(&entry_name); + let Ok(mut file) = file else { + // getInputStream returns null when the entry is not in this zip + return Ok(None.into()); + }; let mut buf = Vec::new(); - file.read_to_end(&mut buf).unwrap(); - - buf + file.read_to_end(&mut buf).map(|_| buf) + }; + let Ok(data) = data else { + return Err(jvm.exception("java/util/zip/ZipException", "invalid entry data").await); }; // TODO do we have to use InflaterInputStream? let mut java_buf = jvm.instantiate_array("B", data.len() as _).await?; - jvm.array_raw_buffer_mut(&mut java_buf).await?.write(0, &data).unwrap(); + jvm.array_raw_buffer_mut(&mut java_buf).await?.write(0, &data)?; let input_stream = jvm.new_class("java/io/ByteArrayInputStream", "([B)V", (java_buf,)).await?; diff --git a/java_runtime/src/loader.rs b/java_runtime/src/loader.rs index c11457a8..cbd2fced 100644 --- a/java_runtime/src/loader.rs +++ b/java_runtime/src/loader.rs @@ -39,6 +39,7 @@ pub fn get_runtime_class_proto(name: &str) -> Option { crate::classes::java::lang::Class::as_proto(), crate::classes::java::lang::ClassCastException::as_proto(), crate::classes::java::lang::ClassLoader::as_proto(), + crate::classes::java::lang::ClassNotFoundException::as_proto(), crate::classes::java::lang::Cloneable::as_proto(), crate::classes::java::lang::CloneNotSupportedException::as_proto(), crate::classes::java::lang::Comparable::as_proto(), @@ -130,6 +131,7 @@ pub fn get_runtime_class_proto(name: &str) -> Option { crate::classes::java::util::jar::JarFileEntries::as_proto(), crate::classes::java::util::jar::Manifest::as_proto(), crate::classes::java::util::zip::ZipEntry::as_proto(), + crate::classes::java::util::zip::ZipException::as_proto(), crate::classes::java::util::zip::ZipFile::as_proto(), crate::classes::java::util::zip::ZipFileEntries::as_proto(), crate::classes::org::rustjava::net::FileURLConnection::as_proto(), diff --git a/java_runtime/src/runtime/io.rs b/java_runtime/src/runtime/io.rs index 9d7ffd24..9b460876 100644 --- a/java_runtime/src/runtime/io.rs +++ b/java_runtime/src/runtime/io.rs @@ -19,6 +19,7 @@ impl FileDescriptorId { pub enum IOError { Unsupported, NotFound, + Io, } pub type IOResult = Result; diff --git a/src/runtime.rs b/src/runtime.rs index 649fd6e7..921baa4c 100644 --- a/src/runtime.rs +++ b/src/runtime.rs @@ -130,8 +130,8 @@ where } async fn open(&self, path: &str, write: bool) -> IOResult { - let file = Box::new(FileImpl::new(path, write)); - Ok(self.register_file(file)) + let file = FileImpl::new(path, write).map_err(|_| IOError::NotFound)?; + Ok(self.register_file(Box::new(file))) } fn get_file(&self, fd: FileDescriptorId) -> IOResult> { diff --git a/src/runtime/io.rs b/src/runtime/io.rs index 6567c092..61243625 100644 --- a/src/runtime/io.rs +++ b/src/runtime/io.rs @@ -35,7 +35,7 @@ where } async fn write(&mut self, buf: &[u8]) -> IOResult { - let written = self.write.lock().unwrap().write(buf).unwrap(); + let written = self.write.lock().unwrap().write(buf).map_err(|_| IOError::Io)?; Ok(written) } @@ -90,7 +90,7 @@ where R: Read + Send + Sync + 'static, { async fn read(&mut self, buf: &mut [u8]) -> IOResult { - let read = self.read.lock().unwrap().read(buf).unwrap(); + let read = self.read.lock().unwrap().read(buf).map_err(|_| IOError::Io)?; Ok(read) } @@ -131,50 +131,50 @@ pub struct FileImpl { } impl FileImpl { - pub fn new(path: &str, write: bool) -> Self { + pub fn new(path: &str, write: bool) -> io::Result { let mut options = OpenOptions::new(); - let file = options.read(true).write(write).create(write).open(path).unwrap(); + let file = options.read(true).write(write).create(write).open(path)?; - Self { + Ok(Self { file: Arc::new(Mutex::new(file)), - } + }) } } #[async_trait::async_trait] impl File for FileImpl { async fn read(&mut self, buf: &mut [u8]) -> Result { - let read = self.file.lock().unwrap().read(buf).unwrap(); + let read = self.file.lock().unwrap().read(buf).map_err(|_| IOError::Io)?; Ok(read) } async fn write(&mut self, buf: &[u8]) -> Result { - let write = self.file.lock().unwrap().write(buf).unwrap(); + let write = self.file.lock().unwrap().write(buf).map_err(|_| IOError::Io)?; Ok(write) } async fn seek(&mut self, pos: FileSize) -> Result<(), IOError> { - self.file.lock().unwrap().seek(io::SeekFrom::Start(pos)).unwrap(); + self.file.lock().unwrap().seek(io::SeekFrom::Start(pos)).map_err(|_| IOError::Io)?; Ok(()) } async fn tell(&self) -> Result { - let pos = self.file.lock().unwrap().seek(io::SeekFrom::Current(0)).unwrap(); + let pos = self.file.lock().unwrap().seek(io::SeekFrom::Current(0)).map_err(|_| IOError::Io)?; Ok(pos as FileSize) } async fn set_len(&mut self, len: FileSize) -> Result<(), IOError> { - self.file.lock().unwrap().set_len(len).unwrap(); + self.file.lock().unwrap().set_len(len).map_err(|_| IOError::Io)?; Ok(()) } async fn metadata(&self) -> Result { - let metadata = self.file.lock().unwrap().metadata().unwrap(); + let metadata = self.file.lock().unwrap().metadata().map_err(|_| IOError::Io)?; let size = metadata.len(); Ok(FileStat { diff --git a/test_data/FileErrors.class b/test_data/FileErrors.class new file mode 100644 index 0000000000000000000000000000000000000000..b137b9e2d01f647cdaae6bebdcfe3e8dcb880298 GIT binary patch literal 979 zcmZvb+fEZv6o&s@pfeq&&;x}6R*QnPV5#R*kpv7R8U$kvhG0mh?Jk+p&NS1hfhY1B zFKi--58%W22*!VxA~kd__FgmVTmL%D-oO6*_z7SguM7k*q+!^^2!aBUn)SuX*DdEL zzgw!wvM&%^wH@1E6Bx>5_cZ7NQ(ee*^ILXZzHypuzvz3?YG^P7%zmd1lXyi|m4vLH z<{EaZWjjZ?DkBYqFsAa33nVhzSDC3srjXq;pdqS=m_W2g^wQEB!UPf;CQVEs`CsY9 z)0QtAtk`X{fy9M01K(4(S8GAXjKGNF=H!Wi8%SxmY2ucuC4SjKA1*%<{u~nUF&L+MfknG2I+y1|a=~$$9z%T`MjqGLrscm|enUrp_Sz~X?p4w|%%N4+ebRn_oBz)*^t zp67Zkf#KI~MGA~>+m766H%ij`XqD>xGTg9iM_@8@Fi2G(T=cE-@jI*8CoikcAS@R-L@Jpu6NAjtJ8-%{KK_mQOz#2F2NXIzyVJR%`H#|zpD{mn2XaeNMK z{~V$H*k}jg4kDkvAs(Clj@hp;x0QXH_BrM+pu1fta}CONp=GMXavkKa`5)4Mh9M@L z<{?UB0c*TfX}rZDEbf7Bp&N*hFc8HkxiOL^dkT||u;?gbCKwgN2I7pI#2!!dA!ewM fM2QhK%;F^#q6mK0kk${>!n>H(*>!=A09OA3ryJVp literal 0 HcmV?d00001 diff --git a/test_data/FileErrors.txt b/test_data/FileErrors.txt new file mode 100644 index 00000000..1ef96a68 --- /dev/null +++ b/test_data/FileErrors.txt @@ -0,0 +1,3 @@ +fnf-in +fnf-out +fnf-raf diff --git a/test_data/FileLength.class b/test_data/FileLength.class new file mode 100644 index 0000000000000000000000000000000000000000..5b0cd01b8bd2df56c088ca4262f45c1a5a89e4e8 GIT binary patch literal 569 zcmZvZ-%0{O6vn^NwbgCQGVQ;b{aq{b0g51mE<`FYgzRe7(I$4+aM#GkstbdH9-xPc z&auLR=3?fYneY3ZGc)fWFK+-&P%sceSchgJf(e0{Tl?NFI=0s-o;Pk~GZ2_KQl1Kq z1;SRorXwm~jzp?kJXMa2!Vp+!$u_H`Gbp&q_m$TvwCT`*4wKPS0x7F9hF60gX%6$X z7~+`GF>4}$xj*%*13!>1mF@Pai`18yie8X2AiuQTL*}PU%wvK2ln=@e{868E14~%e zv0`EsX@R+)j(?d2^gBj5o`7c6^EHNLO{^hDFh_cw;AR9XGi-xNo2)GJD{KoGeEZ6` zOe=L;QXpPYo;>Tj4cWW28xHMguC1uRymkHG?TJ$D_IpjqEV7T02i!4o*rNa85rW_! zqIrk1G_8Wx$nG=5BLxB$&3p`m5knrk6tlEMIU};K(617)XG~U}FugZKa)`yRC^N+R Y7?OZS@(2vVCFw~rh!Sd#_CnbI1ephN2><{9 literal 0 HcmV?d00001 diff --git a/test_data/FileLength.txt b/test_data/FileLength.txt new file mode 100644 index 00000000..e05b13f5 --- /dev/null +++ b/test_data/FileLength.txt @@ -0,0 +1,2 @@ +false +0 diff --git a/test_data/ForName.class b/test_data/ForName.class new file mode 100644 index 0000000000000000000000000000000000000000..86295c2f73f01cdf686f70cb5427acaaa936f4f7 GIT binary patch literal 763 zcmZuvU2hUW6g>kAv#i@vDXm|$P-}ImcJbp8V`3V4LNvkPgAZodPH`(cL-xbe{vn_1 zLp4751N>3OI}|FKZnAUl+_~r6b0+ia_m7_dp5uvw9P%8Nivo%aD@Wp!XpMy$wO$R6 zWRNlxpN1+-pE2aU=AZ-{B@V~M6}bPj_dh48jHpXz1X}A(i9+3a9fvCIr?C`K+s5(? z<7a6&_6IaT#|~C-m80Tf6;+1vUkUqTkt7WKP{%zHNroNooOddaNJBMhHy27%K-&yzw`}d}19D#OhD!Fb zXei@1VmPLX6^W1}t$XhlE?|Bq0}|9vMey;Zn9TZ}i&+WB!@1Y#O36}+kQCR4t%NLuxdjPPGQTOb-tB Y=#(S(o@3jFPaBIUTJ%4nJcq}B03@cV00000 literal 0 HcmV?d00001 diff --git a/test_data/ForName.txt b/test_data/ForName.txt new file mode 100644 index 00000000..f4c76f60 --- /dev/null +++ b/test_data/ForName.txt @@ -0,0 +1,2 @@ +java.util.Vector +cnfe diff --git a/test_data/SurrogateChars.class b/test_data/SurrogateChars.class new file mode 100644 index 0000000000000000000000000000000000000000..4d82419fbe9c1e9049ff23ef9e1aeefe26f8043b GIT binary patch literal 900 zcmZvaU2oD*7{~vImKACRER2abbiS<}Y|PgwOp{4sLW08t$3k{hwgc+|g>>zVxB7|n zf*Cb_0B^kY%8z1v4ka!GdU4+R{QuAIdCvLy_v;S;J9w@l1WAFcA&dov#EErg6?>M` zE!Nv7{Lo`q*tQ+pd%++XW>Z0gA$<|l@Lb#JzVtgC?y68ANx*M1RiJ6Oj2OwS;gCB= z3|XUM*5)hAr22y>u0U6i)R4mB6iVa$smJ?7Fz^XzF>tX5#W$4PBLr^s%OdBJhO5X> z&K`HV-tic@sv?W)3YInGaDyQ^Mwk{eC_`b@a~Nc!YBq)0iiTUb%@FYhL6riQ&xZ`Mll{Hgj4+u^%@zs=pZR=Y;C7MO(FUTYWK7NDJI!CmY*VPd&>2VtNKO>PJAw9yiV0CkZJCnHA1$}iA zQurUDuYQ3s!bTtv0mbn5a7)tFy_T%U_jV`ZlAMv(rSJ$3M2d_Y7+ips5F;cB5rRt* oUXIWW+HDg07Txp?G}P%z_7Mk10uH_GchKs?6S6InI=J24w6muAhkV8d zn@Hjh@JAWXtOYc>$;{k)&%NiKz4yn@Z{Gnt#se2Q%t_=u6i^gcJPAGoei%dp|3&Xa z^^HLBp^mh9Brw-(?MpZUwJa$$I`lv3#DA@mr#e&;u7H<8I`(bEfhVwH)Yu#y2PVj3 z9Tg4CySAAa7bRS=Z5PPc>|8ePnvsqMyRCf}5*3?K6{yTIX4FJWxQd#@vWII}`Ad2C z Date: Thu, 16 Jul 2026 18:05:29 +0900 Subject: [PATCH 3/6] Return the same Thread object from Thread.currentThread() (#175) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every attached thread now owns its java/lang/Thread instance: attach takes the instance for threads started via Thread.start (so currentThread() inside run() is the started Thread object) and creates one otherwise (bootstrap, external attachers). currentThread() returns the stored instance, and the GC roots it per thread. Also parse unrecognized classfile attributes as an opaque Unknown variant instead of failing โ€” JVMS 4.7.1 requires silently ignoring them, and the anonymous-class fixture carries EnclosingMethod and Signature attributes the parser rejected. Expected output for the fixture is generated by a real JVM. --- classfile/src/attribute.rs | 4 +++- java_runtime/src/classes/java/lang/thread.rs | 8 +++----- .../tests/classes/java/lang/test_object.rs | 4 ++-- jvm/src/garbage_collector.rs | 4 ++++ jvm/src/jvm.rs | 17 +++++++++++++++-- jvm/src/thread.rs | 15 ++++++++++++++- test_data/CurrentThread$1.class | Bin 0 -> 592 bytes test_data/CurrentThread.class | Bin 0 -> 1009 bytes test_data/CurrentThread.txt | 3 +++ test_data/src/CurrentThread.java | 18 ++++++++++++++++++ 10 files changed, 62 insertions(+), 11 deletions(-) create mode 100644 test_data/CurrentThread$1.class create mode 100644 test_data/CurrentThread.class create mode 100644 test_data/CurrentThread.txt create mode 100644 test_data/src/CurrentThread.java diff --git a/classfile/src/attribute.rs b/classfile/src/attribute.rs index 9dbf6ea2..645abe01 100644 --- a/classfile/src/attribute.rs +++ b/classfile/src/attribute.rs @@ -146,6 +146,7 @@ pub enum AttributeInfo { MethodParameters(Vec), // TODO NestMembers(Vec), // TODO NestHost(Vec), // TODO + Unknown(Arc, Vec), } impl AttributeInfo { @@ -170,7 +171,8 @@ impl AttributeInfo { "MethodParameters" => AttributeInfo::MethodParameters(info.to_vec()), "NestMembers" => AttributeInfo::NestMembers(info.to_vec()), "NestHost" => AttributeInfo::NestHost(info.to_vec()), - _ => return Err(nom::Err::Error(nom::error_position!(info, nom::error::ErrorKind::Switch))), + // unrecognized attributes must be silently ignored (JVMS 4.7.1) + _ => AttributeInfo::Unknown(name.clone(), info.to_vec()), }) }, ) diff --git a/java_runtime/src/classes/java/lang/thread.rs b/java_runtime/src/classes/java/lang/thread.rs index d40447d0..aa8ab1f0 100644 --- a/java_runtime/src/classes/java/lang/thread.rs +++ b/java_runtime/src/classes/java/lang/thread.rs @@ -91,7 +91,7 @@ impl Thread { async fn call(&self) -> Result<()> { tracing::trace!("Thread start"); - self.jvm.attach_thread()?; + self.jvm.attach_thread(self.this.instance.clone()).await?; let result: Result<()> = self.jvm.invoke_virtual(&self.this, "run", "()V", []).await; @@ -202,10 +202,8 @@ impl Thread { } async fn current_thread(jvm: &Jvm, _: &mut RuntimeContext) -> Result> { - tracing::warn!("stub java.lang.Thread::currentThread()"); + tracing::debug!("java.lang.Thread::currentThread()"); - let thread = jvm.new_class("java/lang/Thread", "(Z)V", (true,)).await?; - - Ok(thread.into()) + Ok(jvm.current_java_thread().into()) } } diff --git a/java_runtime/tests/classes/java/lang/test_object.rs b/java_runtime/tests/classes/java/lang/test_object.rs index ba0b4a85..ef3c7f33 100644 --- a/java_runtime/tests/classes/java/lang/test_object.rs +++ b/java_runtime/tests/classes/java/lang/test_object.rs @@ -30,7 +30,7 @@ async fn test_wait() -> Result<()> { #[async_trait::async_trait] impl SpawnCallback for Notifier { async fn call(&self) -> Result<()> { - self.jvm.attach_thread()?; + self.jvm.attach_thread(None).await?; self.runtime.sleep(Duration::from_millis(100)).await; self.notified.store(true, Ordering::Relaxed); @@ -77,7 +77,7 @@ async fn test_wait_timeout() -> Result<()> { #[async_trait::async_trait] impl SpawnCallback for Notifier { async fn call(&self) -> Result<()> { - self.jvm.attach_thread()?; + self.jvm.attach_thread(None).await?; self.runtime.sleep(Duration::from_millis(1000)).await; self.notified.store(true, Ordering::Relaxed); diff --git a/jvm/src/garbage_collector.rs b/jvm/src/garbage_collector.rs index fb1dec92..7cc76493 100644 --- a/jvm/src/garbage_collector.rs +++ b/jvm/src/garbage_collector.rs @@ -26,6 +26,10 @@ pub fn determine_garbage( find_reachable_objects(jvm, x, &mut reachable_objects); }); + threads.values().filter_map(|thread| thread.java_thread()).for_each(|x| { + find_reachable_objects(jvm, x, &mut reachable_objects); + }); + interned_strings.iter().for_each(|x| { find_reachable_objects(jvm, x, &mut reachable_objects); }); diff --git a/jvm/src/jvm.rs b/jvm/src/jvm.rs index 41d965d9..8a777f09 100644 --- a/jvm/src/jvm.rs +++ b/jvm/src/jvm.rs @@ -79,7 +79,7 @@ impl Jvm { } // init startup thread - jvm.attach_thread()?; + jvm.attach_thread(None).await?; // set java class for bootstrap classes let classes = jvm.inner.classes.read().values().cloned().collect::>(); @@ -822,11 +822,19 @@ impl Jvm { Ok(()) } - pub fn attach_thread(&self) -> Result<()> { + // every attached thread owns a java/lang/Thread instance; pass the instance for threads + // started from java (Thread.start), or None to create one + pub async fn attach_thread(&self, java_thread: Option>) -> Result<()> { let thread_id = (self.inner.get_current_thread_id)(); self.inner.threads.write().insert(thread_id, JvmThread::new()); self.push_native_frame(); + let java_thread = match java_thread { + Some(x) => x, + None => self.new_class("java/lang/Thread", "(Z)V", (true,)).await?, + }; + self.inner.threads.write().get_mut(&thread_id).unwrap().set_java_thread(java_thread); + Ok(()) } @@ -837,6 +845,11 @@ impl Jvm { Ok(()) } + pub fn current_java_thread(&self) -> Box { + let thread_id = (self.inner.get_current_thread_id)(); + self.inner.threads.read().get(&thread_id).unwrap().java_thread().unwrap().clone() + } + // TODO we need safe, ergonomic api.. pub fn push_native_frame(&self) { let thread_id = (self.inner.get_current_thread_id)(); diff --git a/jvm/src/thread.rs b/jvm/src/thread.rs index e335eeab..77e76d53 100644 --- a/jvm/src/thread.rs +++ b/jvm/src/thread.rs @@ -29,11 +29,24 @@ impl StackFrame { pub struct JvmThread { stack: Vec, + java_thread: Option>, } impl JvmThread { pub fn new() -> Self { - Self { stack: Vec::new() } + Self { + stack: Vec::new(), + java_thread: None, + } + } + + #[allow(clippy::borrowed_box)] // same as jvm.rs; callers pass it to &Box-taking apis + pub fn java_thread(&self) -> Option<&Box> { + self.java_thread.as_ref() + } + + pub fn set_java_thread(&mut self, java_thread: Box) { + self.java_thread = Some(java_thread); } pub fn push_java_frame(&mut self, class: &Class, class_instance: Option>, method: &str) { diff --git a/test_data/CurrentThread$1.class b/test_data/CurrentThread$1.class new file mode 100644 index 0000000000000000000000000000000000000000..a0170410f7f5593065bf049022bac98dc4beda38 GIT binary patch literal 592 zcmZuuT}uK%6g}f^uDh%?uxzAZGdS~t`|e2eLWbnIE8XydK`U1tEi5Bzz_GD{+(P+G zk0I0h-2p?sT>0y2AkUDSdp$%_@~$Ts3gv&AtUNO44ZlzMY}1vZ6^*+>b!H5+?G7Xl zBSl$FMRZTA?MJF7uH9HLw=j{@*ppPQWY6;hR}St(IQ07lcB%7)N*nt)Alor_Npi8R zcBqJ2kK0PML=fKi0S(H&l~SmN$Adrw#7@v?Y3N{%6F&~USAtd=YlH>b9TLWWtm?@t zSoICYGo2XLDJBU){0)i;*mT;Q#+Y{uZJM>JFJRg`GVM9fnkm90A01m$_BM71bri8j Qp0QpLM}!us#QyaC0FkYGF8}}l literal 0 HcmV?d00001 diff --git a/test_data/CurrentThread.class b/test_data/CurrentThread.class new file mode 100644 index 0000000000000000000000000000000000000000..cafa82c16a6ac6da94d865f1c7e36dbff72b89e9 GIT binary patch literal 1009 zcmZuwYi|-k6g>kA1KUNgr4OoAt5tavkk&WG#1Ku4fQ>Xws?lVjle!4AWOp&@57N*2 zp%@$e0sbiQ&O%$TE-*WL?z#7#IeYp2=f^Jqk8n>%06`ATKnP)mMB99C7CWZgFFtR! zMayFdKd_|bJ!A-`GdnsWh;rx#W?)Rwx8FL#++&Ef`i>){cl4Z0XR0SfM`$^UGl+8} z44lR6RGG#{*Arc8X7{Q0{3zA3i(8H*y@p3Ux@8?n%yTRlSi};;oL_#L!@zqgqazu# z^vlc+$1+2_GO2B?q$7p%>f|d73+Y#iuq0INPcs1-hn1!lxzC;VrpO?Wj=0 zGh6R!X3yty6v>21^7T&Zeg^&VhnDDhmMvY5n@1k{@BKJJtcKlpTH=YNM9ogA zFDTO)X6nNAYC>su8H}e=3a8RBU01kR#dR7kbp=4iAWUn9o>u72=pM=rz`oLkAxoMl z2Uu0h8u9bNjg&T#GJGc8`MQ3zcxwlE!qcg R8+XQ7ij;!R!f3$#>n|lj#bE#d literal 0 HcmV?d00001 diff --git a/test_data/CurrentThread.txt b/test_data/CurrentThread.txt new file mode 100644 index 00000000..9e8a46ac --- /dev/null +++ b/test_data/CurrentThread.txt @@ -0,0 +1,3 @@ +true +true +false diff --git a/test_data/src/CurrentThread.java b/test_data/src/CurrentThread.java new file mode 100644 index 00000000..c32b02a0 --- /dev/null +++ b/test_data/src/CurrentThread.java @@ -0,0 +1,18 @@ +public class CurrentThread { + public static void main(String[] args) throws Exception { + Thread a = Thread.currentThread(); + Thread b = Thread.currentThread(); + System.out.println(a == b); + + final Thread[] seen = new Thread[1]; + Thread t = new Thread(new Runnable() { + public void run() { + seen[0] = Thread.currentThread(); + } + }); + t.start(); + t.join(); + System.out.println(seen[0] == t); + System.out.println(seen[0] == a); + } +} From 7f6b1a9c558b50548d246015d0f3e91d7b6bcab0 Mon Sep 17 00:00:00 2001 From: Inseok Lee Date: Thu, 16 Jul 2026 18:21:50 +0900 Subject: [PATCH 4/6] Add Java primitive wrapper classes (#176) * Add Java primitive wrapper classes * Use Character digit semantics for numeric parsing --- java_runtime/src/classes/java/lang.rs | 30 +- java_runtime/src/classes/java/lang/boolean.rs | 151 ++++ java_runtime/src/classes/java/lang/byte.rs | 242 ++++++ .../src/classes/java/lang/character.rs | 786 ++++++++++++++++++ java_runtime/src/classes/java/lang/class.rs | 36 +- .../src/classes/java/lang/comparable.rs | 11 +- java_runtime/src/classes/java/lang/double.rs | 379 +++++++++ java_runtime/src/classes/java/lang/float.rs | 384 +++++++++ java_runtime/src/classes/java/lang/integer.rs | 451 +++++++++- java_runtime/src/classes/java/lang/long.rs | 413 +++++++++ java_runtime/src/classes/java/lang/number.rs | 45 + java_runtime/src/classes/java/lang/short.rs | 225 +++++ java_runtime/src/loader.rs | 8 + java_runtime/tests/classes/java/lang/mod.rs | 8 + .../tests/classes/java/lang/test_boolean.rs | 136 +++ .../tests/classes/java/lang/test_byte.rs | 111 +++ .../tests/classes/java/lang/test_character.rs | 368 ++++++++ .../tests/classes/java/lang/test_class.rs | 112 ++- .../tests/classes/java/lang/test_double.rs | 243 ++++++ .../tests/classes/java/lang/test_float.rs | 233 ++++++ .../tests/classes/java/lang/test_integer.rs | 202 ++++- .../tests/classes/java/lang/test_long.rs | 168 ++++ .../tests/classes/java/lang/test_number.rs | 76 ++ .../tests/classes/java/lang/test_short.rs | 107 +++ jvm/src/runtime/java_lang_class.rs | 22 +- 25 files changed, 4881 insertions(+), 66 deletions(-) create mode 100644 java_runtime/src/classes/java/lang/boolean.rs create mode 100644 java_runtime/src/classes/java/lang/byte.rs create mode 100644 java_runtime/src/classes/java/lang/character.rs create mode 100644 java_runtime/src/classes/java/lang/double.rs create mode 100644 java_runtime/src/classes/java/lang/float.rs create mode 100644 java_runtime/src/classes/java/lang/long.rs create mode 100644 java_runtime/src/classes/java/lang/number.rs create mode 100644 java_runtime/src/classes/java/lang/short.rs create mode 100644 java_runtime/tests/classes/java/lang/test_boolean.rs create mode 100644 java_runtime/tests/classes/java/lang/test_byte.rs create mode 100644 java_runtime/tests/classes/java/lang/test_character.rs create mode 100644 java_runtime/tests/classes/java/lang/test_double.rs create mode 100644 java_runtime/tests/classes/java/lang/test_float.rs create mode 100644 java_runtime/tests/classes/java/lang/test_long.rs create mode 100644 java_runtime/tests/classes/java/lang/test_number.rs create mode 100644 java_runtime/tests/classes/java/lang/test_short.rs diff --git a/java_runtime/src/classes/java/lang.rs b/java_runtime/src/classes/java/lang.rs index 416ca554..5992124a 100644 --- a/java_runtime/src/classes/java/lang.rs +++ b/java_runtime/src/classes/java/lang.rs @@ -2,6 +2,9 @@ mod abstract_method_error; mod arithmetic_exception; mod array_index_out_of_bounds_exception; mod array_store_exception; +mod boolean; +mod byte; +mod character; mod class; mod class_cast_exception; mod class_loader; @@ -9,9 +12,11 @@ mod class_not_found_exception; mod clone_not_supported_exception; mod cloneable; mod comparable; +mod double; mod error; mod exception; mod exception_in_initializer_error; +mod float; mod illegal_argument_exception; mod incompatible_class_change_error; mod index_out_of_bounds_exception; @@ -19,18 +24,21 @@ mod instantiation_error; mod integer; mod interrupted_exception; mod linkage_error; +mod long; mod math; mod negative_array_size_exception; mod no_class_def_found_error; mod no_such_field_error; mod no_such_method_error; mod null_pointer_exception; +mod number; mod number_format_exception; mod object; mod runnable; mod runtime; mod runtime_exception; mod security_exception; +mod short; mod string; mod string_buffer; mod string_index_out_of_bounds_exception; @@ -41,15 +49,17 @@ mod unsupported_operation_exception; pub use self::{ abstract_method_error::AbstractMethodError, arithmetic_exception::ArithmeticException, - array_index_out_of_bounds_exception::ArrayIndexOutOfBoundsException, array_store_exception::ArrayStoreException, class::Class, - class_cast_exception::ClassCastException, class_loader::ClassLoader, class_not_found_exception::ClassNotFoundException, - clone_not_supported_exception::CloneNotSupportedException, cloneable::Cloneable, comparable::Comparable, error::Error, exception::Exception, - exception_in_initializer_error::ExceptionInInitializerError, illegal_argument_exception::IllegalArgumentException, - incompatible_class_change_error::IncompatibleClassChangeError, index_out_of_bounds_exception::IndexOutOfBoundsException, - instantiation_error::InstantiationError, integer::Integer, interrupted_exception::InterruptedException, linkage_error::LinkageError, math::Math, + array_index_out_of_bounds_exception::ArrayIndexOutOfBoundsException, array_store_exception::ArrayStoreException, boolean::Boolean, byte::Byte, + character::Character, class::Class, class_cast_exception::ClassCastException, class_loader::ClassLoader, + class_not_found_exception::ClassNotFoundException, clone_not_supported_exception::CloneNotSupportedException, cloneable::Cloneable, + comparable::Comparable, double::Double, error::Error, exception::Exception, exception_in_initializer_error::ExceptionInInitializerError, + float::Float, illegal_argument_exception::IllegalArgumentException, incompatible_class_change_error::IncompatibleClassChangeError, + index_out_of_bounds_exception::IndexOutOfBoundsException, instantiation_error::InstantiationError, integer::Integer, + interrupted_exception::InterruptedException, linkage_error::LinkageError, long::Long, math::Math, negative_array_size_exception::NegativeArraySizeException, no_class_def_found_error::NoClassDefFoundError, no_such_field_error::NoSuchFieldError, - no_such_method_error::NoSuchMethodError, null_pointer_exception::NullPointerException, number_format_exception::NumberFormatException, - object::Object, runnable::Runnable, runtime::Runtime, runtime_exception::RuntimeException, security_exception::SecurityException, string::String, - string_buffer::StringBuffer, string_index_out_of_bounds_exception::StringIndexOutOfBoundsException, system::System, thread::Thread, - throwable::Throwable, unsupported_operation_exception::UnsupportedOperationException, + no_such_method_error::NoSuchMethodError, null_pointer_exception::NullPointerException, number::Number, + number_format_exception::NumberFormatException, object::Object, runnable::Runnable, runtime::Runtime, runtime_exception::RuntimeException, + security_exception::SecurityException, short::Short, string::String, string_buffer::StringBuffer, + string_index_out_of_bounds_exception::StringIndexOutOfBoundsException, system::System, thread::Thread, throwable::Throwable, + unsupported_operation_exception::UnsupportedOperationException, }; diff --git a/java_runtime/src/classes/java/lang/boolean.rs b/java_runtime/src/classes/java/lang/boolean.rs new file mode 100644 index 00000000..18648c32 --- /dev/null +++ b/java_runtime/src/classes/java/lang/boolean.rs @@ -0,0 +1,151 @@ +use alloc::vec; + +use java_class_proto::{JavaFieldProto, JavaMethodProto}; +use java_constants::{ClassAccessFlags, FieldAccessFlags, MethodAccessFlags}; +use jvm::{ + ClassInstanceRef, Jvm, Result, + runtime::{JavaLangClass, JavaLangString}, +}; + +use crate::{ + RuntimeClassProto, RuntimeContext, + classes::java::lang::{Object, String}, +}; + +// public final class java.lang.Boolean +pub struct Boolean; + +impl Boolean { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/lang/Boolean", + parent_class: Some("java/lang/Object"), + interfaces: vec!["java/io/Serializable"], + methods: vec![ + JavaMethodProto::new("", "()V", Self::clinit, MethodAccessFlags::STATIC), + JavaMethodProto::new("", "(Z)V", Self::init, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("", "(Ljava/lang/String;)V", Self::init_string, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("booleanValue", "()Z", Self::boolean_value, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "valueOf", + "(Ljava/lang/String;)Ljava/lang/Boolean;", + Self::value_of_string, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new("toString", "()Ljava/lang/String;", Self::to_string, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("hashCode", "()I", Self::hash_code, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("equals", "(Ljava/lang/Object;)Z", Self::equals, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "getBoolean", + "(Ljava/lang/String;)Z", + Self::get_boolean, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + ], + fields: vec![ + JavaFieldProto::new( + "TRUE", + "Ljava/lang/Boolean;", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "FALSE", + "Ljava/lang/Boolean;", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "TYPE", + "Ljava/lang/Class;", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new("value", "Z", FieldAccessFlags::PRIVATE | FieldAccessFlags::FINAL), + ], + access_flags: ClassAccessFlags::PUBLIC | ClassAccessFlags::FINAL, + } + } + + async fn clinit(jvm: &Jvm, _: &mut RuntimeContext) -> Result<()> { + let value = jvm.new_class("java/lang/Boolean", "(Z)V", (true,)).await?; + jvm.put_static_field("java/lang/Boolean", "TRUE", "Ljava/lang/Boolean;", value).await?; + + let value = jvm.new_class("java/lang/Boolean", "(Z)V", (false,)).await?; + jvm.put_static_field("java/lang/Boolean", "FALSE", "Ljava/lang/Boolean;", value).await?; + + jvm.put_static_field( + "java/lang/Boolean", + "TYPE", + "Ljava/lang/Class;", + JavaLangClass::from_rust_primitive(jvm, "boolean").await?, + ) + .await + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, value: bool) -> Result<()> { + let _: () = jvm.invoke_special(&this, "java/lang/Object", "", "()V", ()).await?; + jvm.put_field(&mut this, "value", "Z", value).await + } + + async fn init_string(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, value: ClassInstanceRef) -> Result<()> { + let parsed = if value.is_null() { + false + } else { + JavaLangString::to_rust_string(jvm, &value).await?.eq_ignore_ascii_case("true") + }; + + let _: () = jvm.invoke_special(&this, "java/lang/Object", "", "()V", ()).await?; + jvm.put_field(&mut this, "value", "Z", parsed).await + } + + async fn boolean_value(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + jvm.get_field(&this, "value", "Z").await + } + + async fn value_of_string(jvm: &Jvm, _: &mut RuntimeContext, value: ClassInstanceRef) -> Result> { + let parsed = if value.is_null() { + false + } else { + JavaLangString::to_rust_string(jvm, &value).await?.eq_ignore_ascii_case("true") + }; + + if parsed { + jvm.get_static_field("java/lang/Boolean", "TRUE", "Ljava/lang/Boolean;").await + } else { + jvm.get_static_field("java/lang/Boolean", "FALSE", "Ljava/lang/Boolean;").await + } + } + + async fn to_string(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let value: bool = jvm.invoke_virtual(&this, "booleanValue", "()Z", ()).await?; + Ok(JavaLangString::from_rust_string(jvm, if value { "true" } else { "false" }).await?.into()) + } + + async fn hash_code(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let value: bool = jvm.invoke_virtual(&this, "booleanValue", "()Z", ()).await?; + Ok(if value { 1231 } else { 1237 }) + } + + async fn equals(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, other: ClassInstanceRef) -> Result { + if other.is_null() || !jvm.is_instance(&**other, "java/lang/Boolean") { + return Ok(false); + } + + let this_value: bool = jvm.invoke_virtual(&this, "booleanValue", "()Z", ()).await?; + let other_value: bool = jvm.invoke_virtual(&other, "booleanValue", "()Z", ()).await?; + Ok(this_value == other_value) + } + + async fn get_boolean(jvm: &Jvm, _: &mut RuntimeContext, name: ClassInstanceRef) -> Result { + if name.is_null() { + return Ok(false); + } + + let value: ClassInstanceRef = jvm + .invoke_static("java/lang/System", "getProperty", "(Ljava/lang/String;)Ljava/lang/String;", (name,)) + .await?; + if value.is_null() { + return Ok(false); + } + + Ok(JavaLangString::to_rust_string(jvm, &value).await?.eq_ignore_ascii_case("true")) + } +} diff --git a/java_runtime/src/classes/java/lang/byte.rs b/java_runtime/src/classes/java/lang/byte.rs new file mode 100644 index 00000000..b0a28ecf --- /dev/null +++ b/java_runtime/src/classes/java/lang/byte.rs @@ -0,0 +1,242 @@ +use alloc::{format, vec}; + +use java_class_proto::{JavaFieldProto, JavaMethodProto}; +use java_constants::{ClassAccessFlags, FieldAccessFlags, MethodAccessFlags}; +use jvm::{ + ClassInstanceRef, Jvm, Result, + runtime::{JavaLangClass, JavaLangString}, +}; + +use crate::{ + RuntimeClassProto, RuntimeContext, + classes::java::lang::{Object, String}, +}; + +// public final class java.lang.Byte +pub struct Byte; + +impl Byte { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/lang/Byte", + parent_class: Some("java/lang/Number"), + interfaces: vec!["java/lang/Comparable"], + methods: vec![ + JavaMethodProto::new("", "()V", Self::clinit, MethodAccessFlags::STATIC), + JavaMethodProto::new("", "(B)V", Self::init, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("", "(Ljava/lang/String;)V", Self::init_string, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "parseByte", + "(Ljava/lang/String;)B", + Self::parse_byte, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "parseByte", + "(Ljava/lang/String;I)B", + Self::parse_byte_radix, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "valueOf", + "(Ljava/lang/String;)Ljava/lang/Byte;", + Self::value_of_string, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "valueOf", + "(Ljava/lang/String;I)Ljava/lang/Byte;", + Self::value_of_string_radix, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "decode", + "(Ljava/lang/String;)Ljava/lang/Byte;", + Self::decode, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new("byteValue", "()B", Self::byte_value, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("shortValue", "()S", Self::short_value, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("intValue", "()I", Self::int_value, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("longValue", "()J", Self::long_value, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("floatValue", "()F", Self::float_value, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("doubleValue", "()D", Self::double_value, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("toString", "()Ljava/lang/String;", Self::to_string, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "toString", + "(B)Ljava/lang/String;", + Self::to_string_static, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new("hashCode", "()I", Self::hash_code, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("equals", "(Ljava/lang/Object;)Z", Self::equals, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("compareTo", "(Ljava/lang/Byte;)I", Self::compare_to, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("compareTo", "(Ljava/lang/Object;)I", Self::compare_to_object, MethodAccessFlags::PUBLIC), + ], + fields: vec![ + JavaFieldProto::new( + "MIN_VALUE", + "B", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "MAX_VALUE", + "B", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "TYPE", + "Ljava/lang/Class;", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new("value", "B", FieldAccessFlags::PRIVATE | FieldAccessFlags::FINAL), + ], + access_flags: ClassAccessFlags::PUBLIC | ClassAccessFlags::FINAL, + } + } + + async fn clinit(jvm: &Jvm, _: &mut RuntimeContext) -> Result<()> { + jvm.put_static_field("java/lang/Byte", "MIN_VALUE", "B", i8::MIN).await?; + jvm.put_static_field("java/lang/Byte", "MAX_VALUE", "B", i8::MAX).await?; + jvm.put_static_field( + "java/lang/Byte", + "TYPE", + "Ljava/lang/Class;", + JavaLangClass::from_rust_primitive(jvm, "byte").await?, + ) + .await + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, value: i8) -> Result<()> { + let _: () = jvm.invoke_special(&this, "java/lang/Number", "", "()V", ()).await?; + jvm.put_field(&mut this, "value", "B", value).await + } + + async fn init_string(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, value: ClassInstanceRef) -> Result<()> { + let value: i8 = jvm + .invoke_static("java/lang/Byte", "parseByte", "(Ljava/lang/String;)B", (value,)) + .await?; + let mut this = this; + let _: () = jvm.invoke_special(&this, "java/lang/Number", "", "()V", ()).await?; + jvm.put_field(&mut this, "value", "B", value).await + } + + async fn parse_byte(jvm: &Jvm, _: &mut RuntimeContext, value: ClassInstanceRef) -> Result { + let parsed: i32 = jvm + .invoke_static("java/lang/Integer", "parseInt", "(Ljava/lang/String;)I", (value,)) + .await?; + if !(i32::from(i8::MIN)..=i32::from(i8::MAX)).contains(&parsed) { + return Err(jvm.exception("java/lang/NumberFormatException", "Value out of range").await); + } + Ok(parsed as i8) + } + + async fn parse_byte_radix(jvm: &Jvm, _: &mut RuntimeContext, value: ClassInstanceRef, radix: i32) -> Result { + let parsed: i32 = jvm + .invoke_static("java/lang/Integer", "parseInt", "(Ljava/lang/String;I)I", (value, radix)) + .await?; + if !(i32::from(i8::MIN)..=i32::from(i8::MAX)).contains(&parsed) { + return Err(jvm.exception("java/lang/NumberFormatException", "Value out of range").await); + } + Ok(parsed as i8) + } + + async fn value_of_string(jvm: &Jvm, _: &mut RuntimeContext, value: ClassInstanceRef) -> Result> { + let value: i8 = jvm + .invoke_static("java/lang/Byte", "parseByte", "(Ljava/lang/String;)B", (value,)) + .await?; + Ok(jvm.new_class("java/lang/Byte", "(B)V", (value,)).await?.into()) + } + + async fn value_of_string_radix(jvm: &Jvm, _: &mut RuntimeContext, value: ClassInstanceRef, radix: i32) -> Result> { + let value: i8 = jvm + .invoke_static("java/lang/Byte", "parseByte", "(Ljava/lang/String;I)B", (value, radix)) + .await?; + Ok(jvm.new_class("java/lang/Byte", "(B)V", (value,)).await?.into()) + } + + async fn decode(jvm: &Jvm, _: &mut RuntimeContext, value: ClassInstanceRef) -> Result> { + let integer = jvm + .invoke_static("java/lang/Integer", "decode", "(Ljava/lang/String;)Ljava/lang/Integer;", (value,)) + .await?; + let value: i32 = jvm.invoke_virtual(&integer, "intValue", "()I", ()).await?; + if !(i32::from(i8::MIN)..=i32::from(i8::MAX)).contains(&value) { + return Err(jvm.exception("java/lang/NumberFormatException", "Value out of range").await); + } + Ok(jvm.new_class("java/lang/Byte", "(B)V", (value as i8,)).await?.into()) + } + + async fn byte_value(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + jvm.get_field(&this, "value", "B").await + } + + async fn short_value(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let value: i8 = jvm.invoke_virtual(&this, "byteValue", "()B", ()).await?; + Ok(value as i16) + } + + async fn int_value(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let value: i8 = jvm.invoke_virtual(&this, "byteValue", "()B", ()).await?; + Ok(value as i32) + } + + async fn long_value(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let value: i8 = jvm.invoke_virtual(&this, "byteValue", "()B", ()).await?; + Ok(value as i64) + } + + async fn float_value(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let value: i8 = jvm.invoke_virtual(&this, "byteValue", "()B", ()).await?; + Ok(value as f32) + } + + async fn double_value(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let value: i8 = jvm.invoke_virtual(&this, "byteValue", "()B", ()).await?; + Ok(value as f64) + } + + async fn to_string(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let value: i8 = jvm.invoke_virtual(&this, "byteValue", "()B", ()).await?; + Ok(JavaLangString::from_rust_string(jvm, &format!("{value}")).await?.into()) + } + + async fn to_string_static(jvm: &Jvm, _: &mut RuntimeContext, value: i8) -> Result> { + Ok(JavaLangString::from_rust_string(jvm, &format!("{value}")).await?.into()) + } + + async fn hash_code(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let value: i8 = jvm.invoke_virtual(&this, "byteValue", "()B", ()).await?; + Ok(value as i32) + } + + async fn equals(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, other: ClassInstanceRef) -> Result { + if other.is_null() || !jvm.is_instance(&**other, "java/lang/Byte") { + return Ok(false); + } + let left: i8 = jvm.invoke_virtual(&this, "byteValue", "()B", ()).await?; + let right: i8 = jvm.invoke_virtual(&other, "byteValue", "()B", ()).await?; + Ok(left == right) + } + + async fn compare_to(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, other: ClassInstanceRef) -> Result { + if other.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "other").await); + } + let left: i8 = jvm.invoke_virtual(&this, "byteValue", "()B", ()).await?; + let right: i8 = jvm.invoke_virtual(&other, "byteValue", "()B", ()).await?; + Ok(left.cmp(&right) as i32) + } + + async fn compare_to_object(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, other: ClassInstanceRef) -> Result { + if other.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "other").await); + } + if !jvm.is_instance(&**other, "java/lang/Byte") { + return Err(jvm.exception("java/lang/ClassCastException", "not Byte").await); + } + let other = ClassInstanceRef::::from(other.instance); + let left: i8 = jvm.invoke_virtual(&this, "byteValue", "()B", ()).await?; + let right: i8 = jvm.invoke_virtual(&other, "byteValue", "()B", ()).await?; + Ok(left.cmp(&right) as i32) + } +} diff --git a/java_runtime/src/classes/java/lang/character.rs b/java_runtime/src/classes/java/lang/character.rs new file mode 100644 index 00000000..56993d4f --- /dev/null +++ b/java_runtime/src/classes/java/lang/character.rs @@ -0,0 +1,786 @@ +use core::char; + +use alloc::vec; + +use java_class_proto::{JavaFieldProto, JavaMethodProto}; +use java_constants::{ClassAccessFlags, FieldAccessFlags, MethodAccessFlags}; +use jvm::{ClassInstanceRef, JavaChar, Jvm, Result, runtime::JavaLangClass}; + +use crate::{ + RuntimeClassProto, RuntimeContext, + classes::java::lang::{Object, String}, +}; + +// public final class java.lang.Character +pub struct Character; + +impl Character { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/lang/Character", + parent_class: Some("java/lang/Object"), + interfaces: vec!["java/io/Serializable", "java/lang/Comparable"], + methods: vec![ + JavaMethodProto::new("", "()V", Self::clinit, MethodAccessFlags::STATIC), + JavaMethodProto::new("", "(C)V", Self::init, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("charValue", "()C", Self::char_value, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("toString", "()Ljava/lang/String;", Self::to_string, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("hashCode", "()I", Self::hash_code, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("equals", "(Ljava/lang/Object;)Z", Self::equals, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("compareTo", "(Ljava/lang/Character;)I", Self::compare_to, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("compareTo", "(Ljava/lang/Object;)I", Self::compare_to_object, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "isLowerCase", + "(C)Z", + Self::is_lower_case, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "isUpperCase", + "(C)Z", + Self::is_upper_case, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "isTitleCase", + "(C)Z", + Self::is_title_case, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new("isDigit", "(C)Z", Self::is_digit, MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC), + JavaMethodProto::new( + "isDefined", + "(C)Z", + Self::is_defined, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new("isLetter", "(C)Z", Self::is_letter, MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC), + JavaMethodProto::new( + "isLetterOrDigit", + "(C)Z", + Self::is_letter_or_digit, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "isJavaLetter", + "(C)Z", + Self::is_java_identifier_start, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "isJavaLetterOrDigit", + "(C)Z", + Self::is_java_identifier_part, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "isJavaIdentifierStart", + "(C)Z", + Self::is_java_identifier_start, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "isJavaIdentifierPart", + "(C)Z", + Self::is_java_identifier_part, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "isUnicodeIdentifierStart", + "(C)Z", + Self::is_unicode_identifier_start, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "isUnicodeIdentifierPart", + "(C)Z", + Self::is_unicode_identifier_part, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "isIdentifierIgnorable", + "(C)Z", + Self::is_identifier_ignorable, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new("isSpace", "(C)Z", Self::is_space, MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC), + JavaMethodProto::new( + "isSpaceChar", + "(C)Z", + Self::is_space_char, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "isWhitespace", + "(C)Z", + Self::is_whitespace, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "isISOControl", + "(C)Z", + Self::is_iso_control, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "toLowerCase", + "(C)C", + Self::to_lower_case, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "toUpperCase", + "(C)C", + Self::to_upper_case, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "toTitleCase", + "(C)C", + Self::to_title_case, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new("digit", "(CI)I", Self::digit, MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC), + JavaMethodProto::new( + "forDigit", + "(II)C", + Self::for_digit, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "getNumericValue", + "(C)I", + Self::get_numeric_value, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new("getType", "(C)I", Self::get_type, MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC), + ], + fields: vec![ + JavaFieldProto::new( + "MIN_VALUE", + "C", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "MAX_VALUE", + "C", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "MIN_RADIX", + "I", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "MAX_RADIX", + "I", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "TYPE", + "Ljava/lang/Class;", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "UNASSIGNED", + "B", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "UPPERCASE_LETTER", + "B", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "LOWERCASE_LETTER", + "B", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "TITLECASE_LETTER", + "B", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "MODIFIER_LETTER", + "B", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "OTHER_LETTER", + "B", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "NON_SPACING_MARK", + "B", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "ENCLOSING_MARK", + "B", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "COMBINING_SPACING_MARK", + "B", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "DECIMAL_DIGIT_NUMBER", + "B", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "LETTER_NUMBER", + "B", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "OTHER_NUMBER", + "B", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "SPACE_SEPARATOR", + "B", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "LINE_SEPARATOR", + "B", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "PARAGRAPH_SEPARATOR", + "B", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "CONTROL", + "B", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "FORMAT", + "B", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "PRIVATE_USE", + "B", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "SURROGATE", + "B", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "DASH_PUNCTUATION", + "B", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "START_PUNCTUATION", + "B", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "END_PUNCTUATION", + "B", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "CONNECTOR_PUNCTUATION", + "B", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "OTHER_PUNCTUATION", + "B", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "MATH_SYMBOL", + "B", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "CURRENCY_SYMBOL", + "B", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "MODIFIER_SYMBOL", + "B", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "OTHER_SYMBOL", + "B", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new("value", "C", FieldAccessFlags::PRIVATE | FieldAccessFlags::FINAL), + ], + access_flags: ClassAccessFlags::PUBLIC | ClassAccessFlags::FINAL, + } + } + + async fn clinit(jvm: &Jvm, _: &mut RuntimeContext) -> Result<()> { + jvm.put_static_field("java/lang/Character", "MIN_VALUE", "C", JavaChar::MIN).await?; + jvm.put_static_field("java/lang/Character", "MAX_VALUE", "C", JavaChar::MAX).await?; + jvm.put_static_field("java/lang/Character", "MIN_RADIX", "I", 2i32).await?; + jvm.put_static_field("java/lang/Character", "MAX_RADIX", "I", 36i32).await?; + jvm.put_static_field( + "java/lang/Character", + "TYPE", + "Ljava/lang/Class;", + JavaLangClass::from_rust_primitive(jvm, "char").await?, + ) + .await?; + + for (name, value) in [ + ("UNASSIGNED", 0i8), + ("UPPERCASE_LETTER", 1), + ("LOWERCASE_LETTER", 2), + ("TITLECASE_LETTER", 3), + ("MODIFIER_LETTER", 4), + ("OTHER_LETTER", 5), + ("NON_SPACING_MARK", 6), + ("ENCLOSING_MARK", 7), + ("COMBINING_SPACING_MARK", 8), + ("DECIMAL_DIGIT_NUMBER", 9), + ("LETTER_NUMBER", 10), + ("OTHER_NUMBER", 11), + ("SPACE_SEPARATOR", 12), + ("LINE_SEPARATOR", 13), + ("PARAGRAPH_SEPARATOR", 14), + ("CONTROL", 15), + ("FORMAT", 16), + ("PRIVATE_USE", 18), + ("SURROGATE", 19), + ("DASH_PUNCTUATION", 20), + ("START_PUNCTUATION", 21), + ("END_PUNCTUATION", 22), + ("CONNECTOR_PUNCTUATION", 23), + ("OTHER_PUNCTUATION", 24), + ("MATH_SYMBOL", 25), + ("CURRENCY_SYMBOL", 26), + ("MODIFIER_SYMBOL", 27), + ("OTHER_SYMBOL", 28), + ] { + jvm.put_static_field("java/lang/Character", name, "B", value).await?; + } + + Ok(()) + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, value: JavaChar) -> Result<()> { + let _: () = jvm.invoke_special(&this, "java/lang/Object", "", "()V", ()).await?; + jvm.put_field(&mut this, "value", "C", value).await + } + + async fn char_value(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + jvm.get_field(&this, "value", "C").await + } + + async fn to_string(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let value: JavaChar = jvm.invoke_virtual(&this, "charValue", "()C", ()).await?; + jvm.invoke_static("java/lang/String", "valueOf", "(C)Ljava/lang/String;", (value,)).await + } + + async fn hash_code(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let value: JavaChar = jvm.invoke_virtual(&this, "charValue", "()C", ()).await?; + Ok(i32::from(value)) + } + + async fn equals(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, other: ClassInstanceRef) -> Result { + if other.is_null() || !jvm.is_instance(&**other, "java/lang/Character") { + return Ok(false); + } + + let this_value: JavaChar = jvm.invoke_virtual(&this, "charValue", "()C", ()).await?; + let other_value: JavaChar = jvm.invoke_virtual(&other, "charValue", "()C", ()).await?; + Ok(this_value == other_value) + } + + async fn compare_to(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, other: ClassInstanceRef) -> Result { + if other.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "other").await); + } + if !jvm.is_instance(&**other, "java/lang/Character") { + return Err(jvm.exception("java/lang/ClassCastException", "not Character").await); + } + + let this_value: JavaChar = jvm.invoke_virtual(&this, "charValue", "()C", ()).await?; + let other_value: JavaChar = jvm.invoke_virtual(&other, "charValue", "()C", ()).await?; + Ok(this_value.cmp(&other_value) as i32) + } + + async fn compare_to_object(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, other: ClassInstanceRef) -> Result { + if other.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "other").await); + } + if !jvm.is_instance(&**other, "java/lang/Character") { + return Err(jvm.exception("java/lang/ClassCastException", "not Character").await); + } + + let other = ClassInstanceRef::::from(other.instance); + let this_value: JavaChar = jvm.invoke_virtual(&this, "charValue", "()C", ()).await?; + let other_value: JavaChar = jvm.invoke_virtual(&other, "charValue", "()C", ()).await?; + Ok(this_value.cmp(&other_value) as i32) + } + + async fn is_lower_case(_: &Jvm, _: &mut RuntimeContext, value: JavaChar) -> Result { + Ok(char::from_u32(u32::from(value)).is_some_and(char::is_lowercase)) + } + + async fn is_upper_case(_: &Jvm, _: &mut RuntimeContext, value: JavaChar) -> Result { + Ok(char::from_u32(u32::from(value)).is_some_and(char::is_uppercase)) + } + + async fn is_title_case(_: &Jvm, _: &mut RuntimeContext, value: JavaChar) -> Result { + Ok(matches!( + value, + 0x01c5 | 0x01c8 | 0x01cb | 0x01f2 | 0x1f88..=0x1f8f | 0x1f98..=0x1f9f | 0x1fa8..=0x1faf | 0x1fbc | 0x1fcc | 0x1ffc + )) + } + + async fn is_digit(_: &Jvm, _: &mut RuntimeContext, value: JavaChar) -> Result { + Ok(Self::decimal_digit_value(value).is_some()) + } + + async fn is_defined(jvm: &Jvm, _: &mut RuntimeContext, value: JavaChar) -> Result { + let category: i32 = jvm.invoke_static("java/lang/Character", "getType", "(C)I", (value,)).await?; + Ok(category != 0) + } + + async fn is_letter(jvm: &Jvm, _: &mut RuntimeContext, value: JavaChar) -> Result { + let category: i32 = jvm.invoke_static("java/lang/Character", "getType", "(C)I", (value,)).await?; + Ok((1..=5).contains(&category)) + } + + async fn is_letter_or_digit(jvm: &Jvm, _: &mut RuntimeContext, value: JavaChar) -> Result { + let category: i32 = jvm.invoke_static("java/lang/Character", "getType", "(C)I", (value,)).await?; + Ok((1..=5).contains(&category) || category == 9) + } + + async fn is_java_identifier_start(jvm: &Jvm, _: &mut RuntimeContext, value: JavaChar) -> Result { + let category: i32 = jvm.invoke_static("java/lang/Character", "getType", "(C)I", (value,)).await?; + Ok((1..=5).contains(&category) || matches!(category, 10 | 23 | 26)) + } + + async fn is_java_identifier_part(jvm: &Jvm, _: &mut RuntimeContext, value: JavaChar) -> Result { + let category: i32 = jvm.invoke_static("java/lang/Character", "getType", "(C)I", (value,)).await?; + Ok((1..=10).contains(&category) + || matches!(category, 23 | 26) + || matches!( + value, + 0x0000..=0x0008 | 0x000e..=0x001b | 0x007f..=0x009f | 0x200b..=0x200f | 0x202a..=0x202e | 0x2060..=0x206f | 0xfeff + )) + } + + async fn is_unicode_identifier_start(jvm: &Jvm, _: &mut RuntimeContext, value: JavaChar) -> Result { + let category: i32 = jvm.invoke_static("java/lang/Character", "getType", "(C)I", (value,)).await?; + Ok((1..=5).contains(&category) || category == 10) + } + + async fn is_unicode_identifier_part(jvm: &Jvm, _: &mut RuntimeContext, value: JavaChar) -> Result { + let category: i32 = jvm.invoke_static("java/lang/Character", "getType", "(C)I", (value,)).await?; + Ok((1..=10).contains(&category) + || category == 23 + || matches!( + value, + 0x0000..=0x0008 | 0x000e..=0x001b | 0x007f..=0x009f | 0x200b..=0x200f | 0x202a..=0x202e | 0x2060..=0x206f | 0xfeff + )) + } + + async fn is_identifier_ignorable(_: &Jvm, _: &mut RuntimeContext, value: JavaChar) -> Result { + Ok(matches!( + value, + 0x0000..=0x0008 | 0x000e..=0x001b | 0x007f..=0x009f | 0x200b..=0x200f | 0x202a..=0x202e | 0x2060..=0x206f | 0xfeff + )) + } + + async fn is_space(_: &Jvm, _: &mut RuntimeContext, value: JavaChar) -> Result { + Ok(matches!(value, 0x0009 | 0x000a | 0x000c | 0x000d | 0x0020)) + } + + async fn is_space_char(_: &Jvm, _: &mut RuntimeContext, value: JavaChar) -> Result { + Ok(matches!( + value, + 0x0020 | 0x00a0 | 0x1680 | 0x2000..=0x200a | 0x2028..=0x2029 | 0x202f | 0x205f | 0x3000 + )) + } + + async fn is_whitespace(_: &Jvm, _: &mut RuntimeContext, value: JavaChar) -> Result { + let Some(value) = char::from_u32(u32::from(value)) else { + return Ok(false); + }; + Ok((value.is_whitespace() && !matches!(value, '\u{00a0}' | '\u{2007}' | '\u{202f}')) || matches!(value, '\u{001c}'..='\u{001f}')) + } + + async fn is_iso_control(_: &Jvm, _: &mut RuntimeContext, value: JavaChar) -> Result { + Ok(matches!(value, 0x0000..=0x001f | 0x007f..=0x009f)) + } + + async fn to_lower_case(_: &Jvm, _: &mut RuntimeContext, value: JavaChar) -> Result { + let Some(character) = char::from_u32(u32::from(value)) else { + return Ok(value); + }; + let mut mapped = character.to_lowercase(); + let Some(first) = mapped.next() else { + return Ok(value); + }; + if mapped.next().is_some() || u32::from(first) > u32::from(JavaChar::MAX) { + return Ok(value); + } + Ok(first as JavaChar) + } + + async fn to_upper_case(_: &Jvm, _: &mut RuntimeContext, value: JavaChar) -> Result { + let Some(character) = char::from_u32(u32::from(value)) else { + return Ok(value); + }; + let mut mapped = character.to_uppercase(); + let Some(first) = mapped.next() else { + return Ok(value); + }; + if mapped.next().is_some() || u32::from(first) > u32::from(JavaChar::MAX) { + return Ok(value); + } + Ok(first as JavaChar) + } + + async fn to_title_case(_: &Jvm, _: &mut RuntimeContext, value: JavaChar) -> Result { + let title = match value { + 0x01c4..=0x01c6 => 0x01c5, + 0x01c7..=0x01c9 => 0x01c8, + 0x01ca..=0x01cc => 0x01cb, + 0x01f1..=0x01f3 => 0x01f2, + _ => { + let Some(character) = char::from_u32(u32::from(value)) else { + return Ok(value); + }; + let mut mapped = character.to_uppercase(); + let Some(first) = mapped.next() else { + return Ok(value); + }; + if mapped.next().is_some() || u32::from(first) > u32::from(JavaChar::MAX) { + return Ok(value); + } + first as JavaChar + } + }; + Ok(title) + } + + async fn digit(_: &Jvm, _: &mut RuntimeContext, value: JavaChar, radix: i32) -> Result { + Ok(Self::digit_value(value, radix)) + } + + pub(crate) fn digit_value(value: JavaChar, radix: i32) -> i32 { + if !(2..=36).contains(&radix) { + return -1; + } + + let numeric = if let Some(value) = Self::decimal_digit_value(value) { + value + } else if (b'a' as JavaChar..=b'z' as JavaChar).contains(&value) { + i32::from(value - b'a' as JavaChar) + 10 + } else if (b'A' as JavaChar..=b'Z' as JavaChar).contains(&value) { + i32::from(value - b'A' as JavaChar) + 10 + } else if (0xff41..=0xff5a).contains(&value) { + i32::from(value - 0xff41) + 10 + } else if (0xff21..=0xff3a).contains(&value) { + i32::from(value - 0xff21) + 10 + } else { + return -1; + }; + + if numeric < radix { numeric } else { -1 } + } + + async fn for_digit(_: &Jvm, _: &mut RuntimeContext, digit: i32, radix: i32) -> Result { + if !(2..=36).contains(&radix) || !(0..radix).contains(&digit) { + return Ok(0); + } + + Ok(if digit < 10 { + (i32::from(b'0') + digit) as JavaChar + } else { + (i32::from(b'a') + digit - 10) as JavaChar + }) + } + + async fn get_numeric_value(_: &Jvm, _: &mut RuntimeContext, value: JavaChar) -> Result { + if let Some(value) = Self::decimal_digit_value(value) { + return Ok(value); + } + if (b'a' as JavaChar..=b'z' as JavaChar).contains(&value) { + return Ok(i32::from(value - b'a' as JavaChar) + 10); + } + if (b'A' as JavaChar..=b'Z' as JavaChar).contains(&value) { + return Ok(i32::from(value - b'A' as JavaChar) + 10); + } + if (0xff41..=0xff5a).contains(&value) { + return Ok(i32::from(value - 0xff41) + 10); + } + if (0xff21..=0xff3a).contains(&value) { + return Ok(i32::from(value - 0xff21) + 10); + } + + Ok(match value { + 0x00b2 => 2, + 0x00b3 => 3, + 0x00b9 => 1, + 0x2160..=0x216b => i32::from(value - 0x2160) + 1, + 0x2170..=0x217b => i32::from(value - 0x2170) + 1, + 0x00bc..=0x00be => -2, + _ => -1, + }) + } + + async fn get_type(_: &Jvm, _: &mut RuntimeContext, value: JavaChar) -> Result { + if char::from_u32(u32::from(value)).is_none() || matches!(value, 0xfdd0..=0xfdef | 0xfffe | 0xffff) { + return Ok(0); + } + if matches!( + value, + 0x01c5 | 0x01c8 | 0x01cb | 0x01f2 | 0x1f88..=0x1f8f | 0x1f98..=0x1f9f | 0x1fa8..=0x1faf | 0x1fbc | 0x1fcc | 0x1ffc + ) { + return Ok(3); + } + + if matches!(value, 0x2160..=0x2188) { + return Ok(10); + } + if Self::decimal_digit_value(value).is_some() { + return Ok(9); + } + if matches!(value, 0x20dd..=0x20e0) { + return Ok(7); + } + if matches!(value, 0x0300..=0x036f | 0x1ab0..=0x1aff | 0x1dc0..=0x1dff | 0x20d0..=0x20ff | 0xfe00..=0xfe0f | 0xfe20..=0xfe2f) { + return Ok(6); + } + if matches!(value, 0x0903 | 0x093b | 0x093e..=0x0940 | 0x0949..=0x094c) { + return Ok(8); + } + + let character = char::from_u32(u32::from(value)); + if character.is_some_and(char::is_uppercase) { + return Ok(1); + } + if character.is_some_and(char::is_lowercase) { + return Ok(2); + } + if character.is_some_and(char::is_alphabetic) { + return Ok(5); + } + if matches!(value, 0x00b2..=0x00b3 | 0x00b9 | 0x00bc..=0x00be | 0x2070 | 0x2074..=0x2079 | 0x2080..=0x2089) { + return Ok(11); + } + if value == 0x2028 { + return Ok(13); + } + if value == 0x2029 { + return Ok(14); + } + if matches!(value, 0x0020 | 0x00a0 | 0x1680 | 0x2000..=0x200a | 0x202f | 0x205f | 0x3000) { + return Ok(12); + } + if matches!(value, 0x0000..=0x001f | 0x007f..=0x009f) { + return Ok(15); + } + if matches!(value, 0x00ad | 0x200b..=0x200f | 0x202a..=0x202e | 0x2060..=0x206f | 0xfeff) { + return Ok(16); + } + if matches!(value, 0xe000..=0xf8ff) { + return Ok(18); + } + if matches!(value, 0x002d | 0x058a | 0x2010..=0x2015 | 0x2e17 | 0x2e1a | 0x2e3a..=0x2e3b | 0x301c | 0x3030) { + return Ok(20); + } + if matches!(value, 0x0028 | 0x005b | 0x007b | 0x0f3a | 0x0f3c | 0x169b | 0x201a | 0x201e | 0x2045) { + return Ok(21); + } + if matches!(value, 0x0029 | 0x005d | 0x007d | 0x0f3b | 0x0f3d | 0x169c | 0x2046) { + return Ok(22); + } + if value == 0x00ab { + return Ok(21); + } + if value == 0x00bb { + return Ok(22); + } + if matches!(value, 0x005f | 0x203f..=0x2040 | 0x2054 | 0xfe33..=0xfe34 | 0xfe4d..=0xfe4f | 0xff3f) { + return Ok(23); + } + if matches!(value, 0x0024 | 0x00a2..=0x00a5 | 0x058f | 0x060b | 0x09f2..=0x09f3 | 0x0af1 | 0x0bf9 | 0x0e3f | 0x17db | 0x20a0..=0x20cf | 0xa838 | 0xfdfc | 0xfe69 | 0xff04 | 0xffe0..=0xffe6) + { + return Ok(26); + } + if matches!(value, 0x005e | 0x0060 | 0x00a8 | 0x00af | 0x00b4 | 0x00b8 | 0x02b0..=0x02ff) { + return Ok(27); + } + if matches!(value, 0x002b | 0x003c..=0x003e | 0x007c | 0x007e | 0x00ac | 0x00b1 | 0x00d7 | 0x00f7 | 0x2200..=0x22ff) { + return Ok(25); + } + if matches!(value, 0x00a6 | 0x00a9 | 0x00ae | 0x00b0 | 0x2300..=0x23ff | 0x2460..=0x24ff | 0x2600..=0x27bf) { + return Ok(28); + } + if matches!(value, 0x00a1 | 0x00a7 | 0x00b6..=0x00b7 | 0x00bf) { + return Ok(24); + } + if (value <= 0x007e && (value as u8).is_ascii_punctuation()) || matches!(value, 0x2000..=0x206f | 0x3001..=0x303f) { + return Ok(24); + } + + Ok(0) + } + + fn decimal_digit_value(value: JavaChar) -> Option { + let zero = match value { + 0x0030..=0x0039 => 0x0030, + 0x0660..=0x0669 => 0x0660, + 0x06f0..=0x06f9 => 0x06f0, + 0x07c0..=0x07c9 => 0x07c0, + 0x0966..=0x096f => 0x0966, + 0x09e6..=0x09ef => 0x09e6, + 0x0a66..=0x0a6f => 0x0a66, + 0x0ae6..=0x0aef => 0x0ae6, + 0x0b66..=0x0b6f => 0x0b66, + 0x0be6..=0x0bef => 0x0be6, + 0x0c66..=0x0c6f => 0x0c66, + 0x0ce6..=0x0cef => 0x0ce6, + 0x0d66..=0x0d6f => 0x0d66, + 0x0e50..=0x0e59 => 0x0e50, + 0x0ed0..=0x0ed9 => 0x0ed0, + 0x0f20..=0x0f29 => 0x0f20, + 0x1040..=0x1049 => 0x1040, + 0x1090..=0x1099 => 0x1090, + 0x17e0..=0x17e9 => 0x17e0, + 0x1810..=0x1819 => 0x1810, + 0x1946..=0x194f => 0x1946, + 0x19d0..=0x19d9 => 0x19d0, + 0x1a80..=0x1a89 => 0x1a80, + 0x1a90..=0x1a99 => 0x1a90, + 0x1b50..=0x1b59 => 0x1b50, + 0x1bb0..=0x1bb9 => 0x1bb0, + 0x1c40..=0x1c49 => 0x1c40, + 0x1c50..=0x1c59 => 0x1c50, + 0xa620..=0xa629 => 0xa620, + 0xa8d0..=0xa8d9 => 0xa8d0, + 0xa900..=0xa909 => 0xa900, + 0xa9d0..=0xa9d9 => 0xa9d0, + 0xa9f0..=0xa9f9 => 0xa9f0, + 0xaa50..=0xaa59 => 0xaa50, + 0xabf0..=0xabf9 => 0xabf0, + 0xff10..=0xff19 => 0xff10, + _ => return None, + }; + Some(i32::from(value - zero)) + } +} diff --git a/java_runtime/src/classes/java/lang/class.rs b/java_runtime/src/classes/java/lang/class.rs index a2f62d50..009cee29 100644 --- a/java_runtime/src/classes/java/lang/class.rs +++ b/java_runtime/src/classes/java/lang/class.rs @@ -27,6 +27,7 @@ impl Class { methods: vec![ JavaMethodProto::new("", "()V", Self::init, Default::default()), JavaMethodProto::new("getName", "()Ljava/lang/String;", Self::get_name, Default::default()), + JavaMethodProto::new("isPrimitive", "()Z", Self::is_primitive, MethodAccessFlags::PUBLIC), JavaMethodProto::new("isAssignableFrom", "(Ljava/lang/Class;)Z", Self::is_assignable_from, Default::default()), JavaMethodProto::new( "getResourceAsStream", @@ -62,15 +63,42 @@ impl Class { async fn get_name(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { tracing::debug!("java.lang.Class::getName({this:?})"); - let rust_class = JavaLangClass::to_rust_class(jvm, &this).await?; - let result = JavaLangString::from_rust_string(jvm, &rust_class.name().replace('/', ".")).await?; + let class_name = JavaLangClass::name(jvm, &this).await?; + let result = JavaLangString::from_rust_string(jvm, &class_name.replace('/', ".")).await?; Ok(result.into()) } + async fn is_primitive(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let name = JavaLangClass::name(jvm, &this).await?; + Ok(matches!( + name.as_str(), + "boolean" | "byte" | "char" | "short" | "int" | "long" | "float" | "double" + )) + } + async fn is_assignable_from(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, other: ClassInstanceRef) -> Result { tracing::debug!("java.lang.Class::isAssignableFrom({this:?}, {other:?})"); + if other.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "other").await); + } + + let class_name = JavaLangClass::name(jvm, &this).await?; + let other_name = JavaLangClass::name(jvm, &other).await?; + let class_is_primitive = matches!( + class_name.as_str(), + "boolean" | "byte" | "char" | "short" | "int" | "long" | "float" | "double" + ); + let other_is_primitive = matches!( + other_name.as_str(), + "boolean" | "byte" | "char" | "short" | "int" | "long" | "float" | "double" + ); + + if class_is_primitive || other_is_primitive { + return Ok(class_name == other_name); + } + let rust_class = JavaLangClass::to_rust_class(jvm, &this).await?; let other_rust_class = JavaLangClass::to_rust_class(jvm, &other).await?; @@ -101,6 +129,10 @@ impl Class { async fn for_name(jvm: &Jvm, _context: &mut RuntimeContext, name: ClassInstanceRef) -> Result> { tracing::debug!("java.lang.Class::forName({name:?})"); + if name.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "name").await); + } + let rust_name = JavaLangString::to_rust_string(jvm, &name).await?; let qualified_name = rust_name.replace('.', "/"); diff --git a/java_runtime/src/classes/java/lang/comparable.rs b/java_runtime/src/classes/java/lang/comparable.rs index 5acd8d30..68361ee9 100644 --- a/java_runtime/src/classes/java/lang/comparable.rs +++ b/java_runtime/src/classes/java/lang/comparable.rs @@ -1,6 +1,7 @@ use alloc::vec; -use java_constants::ClassAccessFlags; +use java_class_proto::JavaMethodProto; +use java_constants::{ClassAccessFlags, MethodAccessFlags}; use crate::RuntimeClassProto; @@ -13,9 +14,13 @@ impl Comparable { name: "java/lang/Comparable", parent_class: None, interfaces: vec![], - methods: vec![], + methods: vec![JavaMethodProto::new_abstract( + "compareTo", + "(Ljava/lang/Object;)I", + MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT, + )], fields: vec![], - access_flags: ClassAccessFlags::INTERFACE, + access_flags: ClassAccessFlags::PUBLIC | ClassAccessFlags::INTERFACE | ClassAccessFlags::ABSTRACT, } } } diff --git a/java_runtime/src/classes/java/lang/double.rs b/java_runtime/src/classes/java/lang/double.rs new file mode 100644 index 00000000..89fc0fce --- /dev/null +++ b/java_runtime/src/classes/java/lang/double.rs @@ -0,0 +1,379 @@ +use alloc::{format, string::String as RustString, vec}; + +use java_class_proto::{JavaFieldProto, JavaMethodProto}; +use java_constants::{ClassAccessFlags, FieldAccessFlags, MethodAccessFlags}; +use jvm::{ + ClassInstanceRef, Jvm, Result, + runtime::{JavaLangClass, JavaLangString}, +}; + +use crate::{ + RuntimeClassProto, RuntimeContext, + classes::java::lang::{Object, String}, +}; + +// public final class java.lang.Double +pub struct Double; + +impl Double { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/lang/Double", + parent_class: Some("java/lang/Number"), + interfaces: vec!["java/lang/Comparable"], + methods: vec![ + JavaMethodProto::new("", "()V", Self::clinit, MethodAccessFlags::STATIC), + JavaMethodProto::new("", "(D)V", Self::init, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("", "(Ljava/lang/String;)V", Self::init_string, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "parseDouble", + "(Ljava/lang/String;)D", + Self::parse_double, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "valueOf", + "(Ljava/lang/String;)Ljava/lang/Double;", + Self::value_of_string, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new("byteValue", "()B", Self::byte_value, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("shortValue", "()S", Self::short_value, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("intValue", "()I", Self::int_value, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("longValue", "()J", Self::long_value, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("floatValue", "()F", Self::float_value, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("doubleValue", "()D", Self::double_value, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("toString", "()Ljava/lang/String;", Self::to_string, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "toString", + "(D)Ljava/lang/String;", + Self::to_string_static, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new("isNaN", "()Z", Self::is_nan, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "isNaN", + "(D)Z", + Self::is_nan_static, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new("isInfinite", "()Z", Self::is_infinite, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "isInfinite", + "(D)Z", + Self::is_infinite_static, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "doubleToLongBits", + "(D)J", + Self::double_to_long_bits, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "longBitsToDouble", + "(J)D", + Self::long_bits_to_double, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new("hashCode", "()I", Self::hash_code, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("equals", "(Ljava/lang/Object;)Z", Self::equals, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("compareTo", "(Ljava/lang/Double;)I", Self::compare_to, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("compareTo", "(Ljava/lang/Object;)I", Self::compare_to_object, MethodAccessFlags::PUBLIC), + ], + fields: vec![ + JavaFieldProto::new( + "POSITIVE_INFINITY", + "D", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "NEGATIVE_INFINITY", + "D", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new("NaN", "D", FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL), + JavaFieldProto::new( + "MAX_VALUE", + "D", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "MIN_VALUE", + "D", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "TYPE", + "Ljava/lang/Class;", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new("value", "D", FieldAccessFlags::PRIVATE | FieldAccessFlags::FINAL), + ], + access_flags: ClassAccessFlags::PUBLIC | ClassAccessFlags::FINAL, + } + } + + async fn clinit(jvm: &Jvm, _: &mut RuntimeContext) -> Result<()> { + jvm.put_static_field("java/lang/Double", "POSITIVE_INFINITY", "D", f64::INFINITY).await?; + jvm.put_static_field("java/lang/Double", "NEGATIVE_INFINITY", "D", f64::NEG_INFINITY) + .await?; + jvm.put_static_field("java/lang/Double", "NaN", "D", f64::NAN).await?; + jvm.put_static_field("java/lang/Double", "MAX_VALUE", "D", f64::MAX).await?; + jvm.put_static_field("java/lang/Double", "MIN_VALUE", "D", f64::from_bits(1)).await?; + jvm.put_static_field( + "java/lang/Double", + "TYPE", + "Ljava/lang/Class;", + JavaLangClass::from_rust_primitive(jvm, "double").await?, + ) + .await + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, value: f64) -> Result<()> { + let _: () = jvm.invoke_special(&this, "java/lang/Number", "", "()V", ()).await?; + jvm.put_field(&mut this, "value", "D", value).await + } + + async fn init_string(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, value: ClassInstanceRef) -> Result<()> { + if value.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "value").await); + } + let value = JavaLangString::to_rust_string(jvm, &value).await?; + let value = Self::parse_value(jvm, &value).await?; + let _: () = jvm.invoke_special(&this, "java/lang/Number", "", "()V", ()).await?; + jvm.put_field(&mut this, "value", "D", value).await + } + + async fn parse_value(jvm: &Jvm, value: &str) -> Result { + let trimmed = value.trim_matches(|character| character <= '\u{0020}'); + let parsed = match trimmed { + "NaN" | "+NaN" | "-NaN" => Some(f64::NAN), + "Infinity" | "+Infinity" => Some(f64::INFINITY), + "-Infinity" => Some(f64::NEG_INFINITY), + _ => { + let number = match trimmed.as_bytes().last() { + Some(b'f' | b'F' | b'd' | b'D') => &trimmed[..trimmed.len() - 1], + _ => trimmed, + }; + let bytes = number.as_bytes(); + let mut index = 0; + if matches!(bytes.first(), Some(b'+' | b'-')) { + index += 1; + } + + let mut digits = 0; + while index < bytes.len() && bytes[index].is_ascii_digit() { + index += 1; + digits += 1; + } + if index < bytes.len() && bytes[index] == b'.' { + index += 1; + while index < bytes.len() && bytes[index].is_ascii_digit() { + index += 1; + digits += 1; + } + } + + if digits == 0 { + None + } else { + if index < bytes.len() && matches!(bytes[index], b'e' | b'E') { + index += 1; + if index < bytes.len() && matches!(bytes[index], b'+' | b'-') { + index += 1; + } + let exponent_start = index; + while index < bytes.len() && bytes[index].is_ascii_digit() { + index += 1; + } + if exponent_start == index { + index = bytes.len() + 1; + } + } + if index == bytes.len() { number.parse::().ok() } else { None } + } + } + }; + + match parsed { + Some(value) => Ok(value), + None => Err(jvm + .exception("java/lang/NumberFormatException", &format!("For input string: \"{value}\"")) + .await), + } + } + + async fn parse_double(jvm: &Jvm, _: &mut RuntimeContext, value: ClassInstanceRef) -> Result { + if value.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "value").await); + } + let value = JavaLangString::to_rust_string(jvm, &value).await?; + Self::parse_value(jvm, &value).await + } + + async fn value_of_string(jvm: &Jvm, _: &mut RuntimeContext, value: ClassInstanceRef) -> Result> { + if value.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "value").await); + } + let value = JavaLangString::to_rust_string(jvm, &value).await?; + let value = Self::parse_value(jvm, &value).await?; + Ok(jvm.new_class("java/lang/Double", "(D)V", (value,)).await?.into()) + } + + async fn byte_value(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let value: f64 = jvm.invoke_virtual(&this, "doubleValue", "()D", ()).await?; + Ok((value as i32) as i8) + } + + async fn short_value(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let value: f64 = jvm.invoke_virtual(&this, "doubleValue", "()D", ()).await?; + Ok((value as i32) as i16) + } + + async fn int_value(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let value: f64 = jvm.invoke_virtual(&this, "doubleValue", "()D", ()).await?; + Ok(value as i32) + } + + async fn long_value(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let value: f64 = jvm.invoke_virtual(&this, "doubleValue", "()D", ()).await?; + Ok(value as i64) + } + + async fn float_value(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let value: f64 = jvm.invoke_virtual(&this, "doubleValue", "()D", ()).await?; + Ok(value as f32) + } + + async fn double_value(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + jvm.get_field(&this, "value", "D").await + } + + fn format_value(value: f64) -> RustString { + if value.is_nan() { + return "NaN".into(); + } + if value == f64::INFINITY { + return "Infinity".into(); + } + if value == f64::NEG_INFINITY { + return "-Infinity".into(); + } + if value.to_bits() == 1 { + return "4.9E-324".into(); + } + if value.to_bits() == 0x8000_0000_0000_0001 { + return "-4.9E-324".into(); + } + + let magnitude = value.abs(); + let mut result = if value != 0.0 && !(0.001..10_000_000.0).contains(&magnitude) { + format!("{value:e}") + } else { + format!("{value}") + }; + if let Some(exponent_index) = result.find('e') { + let exponent = result.split_off(exponent_index + 1); + result.pop(); + if !result.contains('.') { + result.push_str(".0"); + } + result.push('E'); + result.push_str(exponent.strip_prefix('+').unwrap_or(&exponent)); + } else if !result.contains('.') { + result.push_str(".0"); + } + result + } + + async fn to_string(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let value: f64 = jvm.invoke_virtual(&this, "doubleValue", "()D", ()).await?; + Ok(JavaLangString::from_rust_string(jvm, &Self::format_value(value)).await?.into()) + } + + async fn to_string_static(jvm: &Jvm, _: &mut RuntimeContext, value: f64) -> Result> { + Ok(JavaLangString::from_rust_string(jvm, &Self::format_value(value)).await?.into()) + } + + async fn is_nan(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let value: f64 = jvm.invoke_virtual(&this, "doubleValue", "()D", ()).await?; + Ok(value.is_nan()) + } + + async fn is_nan_static(_: &Jvm, _: &mut RuntimeContext, value: f64) -> Result { + Ok(value.is_nan()) + } + + async fn is_infinite(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let value: f64 = jvm.invoke_virtual(&this, "doubleValue", "()D", ()).await?; + Ok(value.is_infinite()) + } + + async fn is_infinite_static(_: &Jvm, _: &mut RuntimeContext, value: f64) -> Result { + Ok(value.is_infinite()) + } + + async fn double_to_long_bits(_: &Jvm, _: &mut RuntimeContext, value: f64) -> Result { + Ok(if value.is_nan() { 0x7ff8_0000_0000_0000 } else { value.to_bits() as i64 }) + } + + async fn long_bits_to_double(_: &Jvm, _: &mut RuntimeContext, bits: i64) -> Result { + Ok(f64::from_bits(bits as u64)) + } + + async fn hash_code(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let value: f64 = jvm.invoke_virtual(&this, "doubleValue", "()D", ()).await?; + let bits = if value.is_nan() { 0x7ff8_0000_0000_0000 } else { value.to_bits() }; + Ok((bits ^ (bits >> 32)) as i32) + } + + async fn equals(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, other: ClassInstanceRef) -> Result { + if other.is_null() || !jvm.is_instance(&**other, "java/lang/Double") { + return Ok(false); + } + let left: f64 = jvm.invoke_virtual(&this, "doubleValue", "()D", ()).await?; + let right: f64 = jvm.invoke_virtual(&other, "doubleValue", "()D", ()).await?; + let left_bits = if left.is_nan() { 0x7ff8_0000_0000_0000 } else { left.to_bits() as i64 }; + let right_bits = if right.is_nan() { 0x7ff8_0000_0000_0000 } else { right.to_bits() as i64 }; + Ok(left_bits == right_bits) + } + + async fn compare_to(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, other: ClassInstanceRef) -> Result { + if other.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "other").await); + } + let left: f64 = jvm.invoke_virtual(&this, "doubleValue", "()D", ()).await?; + let right: f64 = jvm.invoke_virtual(&other, "doubleValue", "()D", ()).await?; + if left < right { + return Ok(-1); + } + if left > right { + return Ok(1); + } + let left_bits = if left.is_nan() { 0x7ff8_0000_0000_0000 } else { left.to_bits() as i64 }; + let right_bits = if right.is_nan() { 0x7ff8_0000_0000_0000 } else { right.to_bits() as i64 }; + Ok(left_bits.cmp(&right_bits) as i32) + } + + async fn compare_to_object(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, other: ClassInstanceRef) -> Result { + if other.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "other").await); + } + if !jvm.is_instance(&**other, "java/lang/Double") { + return Err(jvm.exception("java/lang/ClassCastException", "not Double").await); + } + let other = ClassInstanceRef::::from(other.instance); + let left: f64 = jvm.invoke_virtual(&this, "doubleValue", "()D", ()).await?; + let right: f64 = jvm.invoke_virtual(&other, "doubleValue", "()D", ()).await?; + if left < right { + return Ok(-1); + } + if left > right { + return Ok(1); + } + let left_bits = if left.is_nan() { 0x7ff8_0000_0000_0000 } else { left.to_bits() as i64 }; + let right_bits = if right.is_nan() { 0x7ff8_0000_0000_0000 } else { right.to_bits() as i64 }; + Ok(left_bits.cmp(&right_bits) as i32) + } +} diff --git a/java_runtime/src/classes/java/lang/float.rs b/java_runtime/src/classes/java/lang/float.rs new file mode 100644 index 00000000..83d0c887 --- /dev/null +++ b/java_runtime/src/classes/java/lang/float.rs @@ -0,0 +1,384 @@ +use alloc::{format, string::String as RustString, vec}; + +use java_class_proto::{JavaFieldProto, JavaMethodProto}; +use java_constants::{ClassAccessFlags, FieldAccessFlags, MethodAccessFlags}; +use jvm::{ + ClassInstanceRef, Jvm, Result, + runtime::{JavaLangClass, JavaLangString}, +}; + +use crate::{ + RuntimeClassProto, RuntimeContext, + classes::java::lang::{Object, String}, +}; + +// public final class java.lang.Float +pub struct Float; + +impl Float { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/lang/Float", + parent_class: Some("java/lang/Number"), + interfaces: vec!["java/lang/Comparable"], + methods: vec![ + JavaMethodProto::new("", "()V", Self::clinit, MethodAccessFlags::STATIC), + JavaMethodProto::new("", "(F)V", Self::init, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("", "(D)V", Self::init_double, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("", "(Ljava/lang/String;)V", Self::init_string, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "parseFloat", + "(Ljava/lang/String;)F", + Self::parse_float, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "valueOf", + "(Ljava/lang/String;)Ljava/lang/Float;", + Self::value_of_string, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new("byteValue", "()B", Self::byte_value, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("shortValue", "()S", Self::short_value, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("intValue", "()I", Self::int_value, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("longValue", "()J", Self::long_value, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("floatValue", "()F", Self::float_value, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("doubleValue", "()D", Self::double_value, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("toString", "()Ljava/lang/String;", Self::to_string, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "toString", + "(F)Ljava/lang/String;", + Self::to_string_static, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new("isNaN", "()Z", Self::is_nan, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "isNaN", + "(F)Z", + Self::is_nan_static, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new("isInfinite", "()Z", Self::is_infinite, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "isInfinite", + "(F)Z", + Self::is_infinite_static, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "floatToIntBits", + "(F)I", + Self::float_to_int_bits, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "intBitsToFloat", + "(I)F", + Self::int_bits_to_float, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new("hashCode", "()I", Self::hash_code, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("equals", "(Ljava/lang/Object;)Z", Self::equals, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("compareTo", "(Ljava/lang/Float;)I", Self::compare_to, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("compareTo", "(Ljava/lang/Object;)I", Self::compare_to_object, MethodAccessFlags::PUBLIC), + ], + fields: vec![ + JavaFieldProto::new( + "POSITIVE_INFINITY", + "F", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "NEGATIVE_INFINITY", + "F", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new("NaN", "F", FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL), + JavaFieldProto::new( + "MAX_VALUE", + "F", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "MIN_VALUE", + "F", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "TYPE", + "Ljava/lang/Class;", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new("value", "F", FieldAccessFlags::PRIVATE | FieldAccessFlags::FINAL), + ], + access_flags: ClassAccessFlags::PUBLIC | ClassAccessFlags::FINAL, + } + } + + async fn clinit(jvm: &Jvm, _: &mut RuntimeContext) -> Result<()> { + jvm.put_static_field("java/lang/Float", "POSITIVE_INFINITY", "F", f32::INFINITY).await?; + jvm.put_static_field("java/lang/Float", "NEGATIVE_INFINITY", "F", f32::NEG_INFINITY) + .await?; + jvm.put_static_field("java/lang/Float", "NaN", "F", f32::NAN).await?; + jvm.put_static_field("java/lang/Float", "MAX_VALUE", "F", f32::MAX).await?; + jvm.put_static_field("java/lang/Float", "MIN_VALUE", "F", f32::from_bits(1)).await?; + jvm.put_static_field( + "java/lang/Float", + "TYPE", + "Ljava/lang/Class;", + JavaLangClass::from_rust_primitive(jvm, "float").await?, + ) + .await + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, value: f32) -> Result<()> { + let _: () = jvm.invoke_special(&this, "java/lang/Number", "", "()V", ()).await?; + jvm.put_field(&mut this, "value", "F", value).await + } + + async fn init_double(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, value: f64) -> Result<()> { + let _: () = jvm.invoke_special(&this, "java/lang/Number", "", "()V", ()).await?; + jvm.put_field(&mut this, "value", "F", value as f32).await + } + + async fn init_string(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, value: ClassInstanceRef) -> Result<()> { + if value.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "value").await); + } + let value = JavaLangString::to_rust_string(jvm, &value).await?; + let value = Self::parse_value(jvm, &value).await?; + let _: () = jvm.invoke_special(&this, "java/lang/Number", "", "()V", ()).await?; + jvm.put_field(&mut this, "value", "F", value).await + } + + async fn parse_value(jvm: &Jvm, value: &str) -> Result { + let trimmed = value.trim_matches(|character| character <= '\u{0020}'); + let parsed = match trimmed { + "NaN" | "+NaN" | "-NaN" => Some(f32::NAN), + "Infinity" | "+Infinity" => Some(f32::INFINITY), + "-Infinity" => Some(f32::NEG_INFINITY), + _ => { + let number = match trimmed.as_bytes().last() { + Some(b'f' | b'F' | b'd' | b'D') => &trimmed[..trimmed.len() - 1], + _ => trimmed, + }; + let bytes = number.as_bytes(); + let mut index = 0; + if matches!(bytes.first(), Some(b'+' | b'-')) { + index += 1; + } + + let mut digits = 0; + while index < bytes.len() && bytes[index].is_ascii_digit() { + index += 1; + digits += 1; + } + if index < bytes.len() && bytes[index] == b'.' { + index += 1; + while index < bytes.len() && bytes[index].is_ascii_digit() { + index += 1; + digits += 1; + } + } + + if digits == 0 { + None + } else { + if index < bytes.len() && matches!(bytes[index], b'e' | b'E') { + index += 1; + if index < bytes.len() && matches!(bytes[index], b'+' | b'-') { + index += 1; + } + let exponent_start = index; + while index < bytes.len() && bytes[index].is_ascii_digit() { + index += 1; + } + if exponent_start == index { + index = bytes.len() + 1; + } + } + if index == bytes.len() { number.parse::().ok() } else { None } + } + } + }; + + match parsed { + Some(value) => Ok(value), + None => Err(jvm + .exception("java/lang/NumberFormatException", &format!("For input string: \"{value}\"")) + .await), + } + } + + async fn parse_float(jvm: &Jvm, _: &mut RuntimeContext, value: ClassInstanceRef) -> Result { + if value.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "value").await); + } + let value = JavaLangString::to_rust_string(jvm, &value).await?; + Self::parse_value(jvm, &value).await + } + + async fn value_of_string(jvm: &Jvm, _: &mut RuntimeContext, value: ClassInstanceRef) -> Result> { + if value.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "value").await); + } + let value = JavaLangString::to_rust_string(jvm, &value).await?; + let value = Self::parse_value(jvm, &value).await?; + Ok(jvm.new_class("java/lang/Float", "(F)V", (value,)).await?.into()) + } + + async fn byte_value(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let value: f32 = jvm.invoke_virtual(&this, "floatValue", "()F", ()).await?; + Ok((value as i32) as i8) + } + + async fn short_value(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let value: f32 = jvm.invoke_virtual(&this, "floatValue", "()F", ()).await?; + Ok((value as i32) as i16) + } + + async fn int_value(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let value: f32 = jvm.invoke_virtual(&this, "floatValue", "()F", ()).await?; + Ok(value as i32) + } + + async fn long_value(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let value: f32 = jvm.invoke_virtual(&this, "floatValue", "()F", ()).await?; + Ok(value as i64) + } + + async fn float_value(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + jvm.get_field(&this, "value", "F").await + } + + async fn double_value(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let value: f32 = jvm.invoke_virtual(&this, "floatValue", "()F", ()).await?; + Ok(value as f64) + } + + fn format_value(value: f32) -> RustString { + if value.is_nan() { + return "NaN".into(); + } + if value == f32::INFINITY { + return "Infinity".into(); + } + if value == f32::NEG_INFINITY { + return "-Infinity".into(); + } + if value.to_bits() == 1 { + return "1.4E-45".into(); + } + if value.to_bits() == 0x8000_0001 { + return "-1.4E-45".into(); + } + + let magnitude = value.abs(); + let mut result = if value != 0.0 && !(0.001..10_000_000.0).contains(&magnitude) { + format!("{value:e}") + } else { + format!("{value}") + }; + if let Some(exponent_index) = result.find('e') { + let exponent = result.split_off(exponent_index + 1); + result.pop(); + if !result.contains('.') { + result.push_str(".0"); + } + result.push('E'); + result.push_str(exponent.strip_prefix('+').unwrap_or(&exponent)); + } else if !result.contains('.') { + result.push_str(".0"); + } + result + } + + async fn to_string(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let value: f32 = jvm.invoke_virtual(&this, "floatValue", "()F", ()).await?; + Ok(JavaLangString::from_rust_string(jvm, &Self::format_value(value)).await?.into()) + } + + async fn to_string_static(jvm: &Jvm, _: &mut RuntimeContext, value: f32) -> Result> { + Ok(JavaLangString::from_rust_string(jvm, &Self::format_value(value)).await?.into()) + } + + async fn is_nan(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let value: f32 = jvm.invoke_virtual(&this, "floatValue", "()F", ()).await?; + Ok(value.is_nan()) + } + + async fn is_nan_static(_: &Jvm, _: &mut RuntimeContext, value: f32) -> Result { + Ok(value.is_nan()) + } + + async fn is_infinite(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let value: f32 = jvm.invoke_virtual(&this, "floatValue", "()F", ()).await?; + Ok(value.is_infinite()) + } + + async fn is_infinite_static(_: &Jvm, _: &mut RuntimeContext, value: f32) -> Result { + Ok(value.is_infinite()) + } + + async fn float_to_int_bits(_: &Jvm, _: &mut RuntimeContext, value: f32) -> Result { + Ok(if value.is_nan() { 0x7fc0_0000 } else { value.to_bits() as i32 }) + } + + async fn int_bits_to_float(_: &Jvm, _: &mut RuntimeContext, bits: i32) -> Result { + Ok(f32::from_bits(bits as u32)) + } + + async fn hash_code(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let value: f32 = jvm.invoke_virtual(&this, "floatValue", "()F", ()).await?; + Ok(if value.is_nan() { 0x7fc0_0000 } else { value.to_bits() as i32 }) + } + + async fn equals(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, other: ClassInstanceRef) -> Result { + if other.is_null() || !jvm.is_instance(&**other, "java/lang/Float") { + return Ok(false); + } + let left: f32 = jvm.invoke_virtual(&this, "floatValue", "()F", ()).await?; + let right: f32 = jvm.invoke_virtual(&other, "floatValue", "()F", ()).await?; + let left_bits = if left.is_nan() { 0x7fc0_0000 } else { left.to_bits() as i32 }; + let right_bits = if right.is_nan() { 0x7fc0_0000 } else { right.to_bits() as i32 }; + Ok(left_bits == right_bits) + } + + async fn compare_to(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, other: ClassInstanceRef) -> Result { + if other.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "other").await); + } + let left: f32 = jvm.invoke_virtual(&this, "floatValue", "()F", ()).await?; + let right: f32 = jvm.invoke_virtual(&other, "floatValue", "()F", ()).await?; + if left < right { + return Ok(-1); + } + if left > right { + return Ok(1); + } + let left_bits = if left.is_nan() { 0x7fc0_0000 } else { left.to_bits() as i32 }; + let right_bits = if right.is_nan() { 0x7fc0_0000 } else { right.to_bits() as i32 }; + Ok(left_bits.cmp(&right_bits) as i32) + } + + async fn compare_to_object(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, other: ClassInstanceRef) -> Result { + if other.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "other").await); + } + if !jvm.is_instance(&**other, "java/lang/Float") { + return Err(jvm.exception("java/lang/ClassCastException", "not Float").await); + } + let other = ClassInstanceRef::::from(other.instance); + let left: f32 = jvm.invoke_virtual(&this, "floatValue", "()F", ()).await?; + let right: f32 = jvm.invoke_virtual(&other, "floatValue", "()F", ()).await?; + if left < right { + return Ok(-1); + } + if left > right { + return Ok(1); + } + let left_bits = if left.is_nan() { 0x7fc0_0000 } else { left.to_bits() as i32 }; + let right_bits = if right.is_nan() { 0x7fc0_0000 } else { right.to_bits() as i32 }; + Ok(left_bits.cmp(&right_bits) as i32) + } +} diff --git a/java_runtime/src/classes/java/lang/integer.rs b/java_runtime/src/classes/java/lang/integer.rs index 0a0490f6..c03ff503 100644 --- a/java_runtime/src/classes/java/lang/integer.rs +++ b/java_runtime/src/classes/java/lang/integer.rs @@ -1,94 +1,453 @@ -use alloc::{format, string::ToString, vec}; +use alloc::{format, string::String as RustString, vec}; use java_class_proto::{JavaFieldProto, JavaMethodProto}; -use java_constants::MethodAccessFlags; -use jvm::{ClassInstanceRef, Jvm, Result, runtime::JavaLangString}; +use java_constants::{ClassAccessFlags, FieldAccessFlags, MethodAccessFlags}; +use jvm::{ + ClassInstanceRef, JavaChar, JavaError, Jvm, Result, + runtime::{JavaLangClass, JavaLangString}, +}; -use crate::{RuntimeClassProto, RuntimeContext, classes::java::lang::String}; +use crate::{ + RuntimeClassProto, RuntimeContext, + classes::java::lang::{Character, Object, String}, +}; -// class java.lang.Integer +// public final class java.lang.Integer pub struct Integer; impl Integer { pub fn as_proto() -> RuntimeClassProto { RuntimeClassProto { name: "java/lang/Integer", - parent_class: Some("java/lang/Object"), + parent_class: Some("java/lang/Number"), interfaces: vec!["java/lang/Comparable"], methods: vec![ - JavaMethodProto::new("", "(I)V", Self::init, Default::default()), - JavaMethodProto::new("parseInt", "(Ljava/lang/String;)I", Self::parse_int, MethodAccessFlags::STATIC), - JavaMethodProto::new("valueOf", "(I)Ljava/lang/Integer;", Self::value_of, MethodAccessFlags::STATIC), - JavaMethodProto::new("intValue", "()I", Self::int_value, Default::default()), - JavaMethodProto::new("toString", "()Ljava/lang/String;", Self::to_string, Default::default()), - JavaMethodProto::new("toString", "(I)Ljava/lang/String;", Self::to_string_static, MethodAccessFlags::STATIC), - JavaMethodProto::new("toHexString", "(I)Ljava/lang/String;", Self::to_hex_string, MethodAccessFlags::STATIC), + JavaMethodProto::new("", "()V", Self::clinit, MethodAccessFlags::STATIC), + JavaMethodProto::new("", "(I)V", Self::init, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("", "(Ljava/lang/String;)V", Self::init_string, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "parseInt", + "(Ljava/lang/String;)I", + Self::parse_int, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "parseInt", + "(Ljava/lang/String;I)I", + Self::parse_int_radix, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "valueOf", + "(I)Ljava/lang/Integer;", + Self::value_of, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "valueOf", + "(Ljava/lang/String;)Ljava/lang/Integer;", + Self::value_of_string, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "valueOf", + "(Ljava/lang/String;I)Ljava/lang/Integer;", + Self::value_of_string_radix, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "decode", + "(Ljava/lang/String;)Ljava/lang/Integer;", + Self::decode, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "getInteger", + "(Ljava/lang/String;)Ljava/lang/Integer;", + Self::get_integer, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "getInteger", + "(Ljava/lang/String;I)Ljava/lang/Integer;", + Self::get_integer_value_default, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "getInteger", + "(Ljava/lang/String;Ljava/lang/Integer;)Ljava/lang/Integer;", + Self::get_integer_object_default, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new("intValue", "()I", Self::int_value, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("longValue", "()J", Self::long_value, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("floatValue", "()F", Self::float_value, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("doubleValue", "()D", Self::double_value, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("toString", "()Ljava/lang/String;", Self::to_string, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "toString", + "(I)Ljava/lang/String;", + Self::to_string_static, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "toString", + "(II)Ljava/lang/String;", + Self::to_string_radix_static, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "toBinaryString", + "(I)Ljava/lang/String;", + Self::to_binary_string, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "toOctalString", + "(I)Ljava/lang/String;", + Self::to_octal_string, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "toHexString", + "(I)Ljava/lang/String;", + Self::to_hex_string, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new("hashCode", "()I", Self::hash_code, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("equals", "(Ljava/lang/Object;)Z", Self::equals, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("compareTo", "(Ljava/lang/Integer;)I", Self::compare_to, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("compareTo", "(Ljava/lang/Object;)I", Self::compare_to_object, MethodAccessFlags::PUBLIC), ], - fields: vec![JavaFieldProto::new("value", "I", Default::default())], - access_flags: Default::default(), + fields: vec![ + JavaFieldProto::new( + "MIN_VALUE", + "I", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "MAX_VALUE", + "I", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "TYPE", + "Ljava/lang/Class;", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new("value", "I", FieldAccessFlags::PRIVATE | FieldAccessFlags::FINAL), + ], + access_flags: ClassAccessFlags::PUBLIC | ClassAccessFlags::FINAL, } } + async fn clinit(jvm: &Jvm, _: &mut RuntimeContext) -> Result<()> { + jvm.put_static_field("java/lang/Integer", "MIN_VALUE", "I", i32::MIN).await?; + jvm.put_static_field("java/lang/Integer", "MAX_VALUE", "I", i32::MAX).await?; + jvm.put_static_field( + "java/lang/Integer", + "TYPE", + "Ljava/lang/Class;", + JavaLangClass::from_rust_primitive(jvm, "int").await?, + ) + .await + } + async fn init(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, value: i32) -> Result<()> { - tracing::debug!("java.lang.Integer::({this:?}, {value:?})"); + let _: () = jvm.invoke_special(&this, "java/lang/Number", "", "()V", ()).await?; + jvm.put_field(&mut this, "value", "I", value).await + } - jvm.put_field(&mut this, "value", "I", value).await?; + async fn init_string(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, value: ClassInstanceRef) -> Result<()> { + if value.is_null() { + return Err(jvm.exception("java/lang/NumberFormatException", "null").await); + } + let value = JavaLangString::to_rust_string(jvm, &value).await?; + let value = Self::parse_value(jvm, &value, 10).await?; + let mut this = this; + let _: () = jvm.invoke_special(&this, "java/lang/Number", "", "()V", ()).await?; + jvm.put_field(&mut this, "value", "I", value).await + } - Ok(()) + fn parse_value_raw(value: &str, radix: u32) -> Option { + if !(2..=36).contains(&radix) || value.is_empty() { + return None; + } + let mut chars = value.chars(); + let negative = match chars.next() { + Some('-') => true, + Some('+') => false, + Some(first) => { + chars = value.chars(); + let _ = first; + false + } + None => return None, + }; + let limit = if negative { i64::from(i32::MIN) } else { -i64::from(i32::MAX) }; + let mut result = 0i64; + let mut count = 0; + for value in chars { + let value = JavaChar::try_from(u32::from(value)).ok()?; + let digit = i64::from(Character::digit_value(value, radix as i32)); + if digit < 0 { + return None; + } + if result < (limit + digit) / i64::from(radix) { + return None; + } + result = result * i64::from(radix) - digit; + count += 1; + } + if count == 0 || (!negative && result == i64::from(i32::MIN)) { + return None; + } + Some(if negative { result as i32 } else { -result as i32 }) } - async fn int_value(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { - tracing::debug!("java.lang.Integer::intValue({this:?})"); + async fn parse_value(jvm: &Jvm, value: &str, radix: u32) -> Result { + match Self::parse_value_raw(value, radix) { + Some(value) => Ok(value), + None => Err(jvm + .exception("java/lang/NumberFormatException", &format!("For input string: \"{value}\"")) + .await), + } + } - let value = jvm.get_field(&this, "value", "I").await?; + async fn parse_int(jvm: &Jvm, _: &mut RuntimeContext, value: ClassInstanceRef) -> Result { + if value.is_null() { + return Err(jvm.exception("java/lang/NumberFormatException", "null").await); + } + let value = JavaLangString::to_rust_string(jvm, &value).await?; + Self::parse_value(jvm, &value, 10).await + } - Ok(value) + async fn parse_int_radix(jvm: &Jvm, _: &mut RuntimeContext, value: ClassInstanceRef, radix: i32) -> Result { + if value.is_null() { + return Err(jvm.exception("java/lang/NumberFormatException", "null").await); + } + let value = JavaLangString::to_rust_string(jvm, &value).await?; + Self::parse_value(jvm, &value, radix as u32).await } async fn value_of(jvm: &Jvm, _: &mut RuntimeContext, value: i32) -> Result> { - tracing::debug!("java.lang.Integer::valueOf({value:?})"); - - let instance = jvm.new_class("java/lang/Integer", "(I)V", (value,)).await?; + Ok(jvm.new_class("java/lang/Integer", "(I)V", (value,)).await?.into()) + } - Ok(instance.into()) + async fn value_of_string(jvm: &Jvm, _: &mut RuntimeContext, value: ClassInstanceRef) -> Result> { + if value.is_null() { + return Err(jvm.exception("java/lang/NumberFormatException", "null").await); + } + let value = JavaLangString::to_rust_string(jvm, &value).await?; + let value = Self::parse_value(jvm, &value, 10).await?; + Ok(jvm.new_class("java/lang/Integer", "(I)V", (value,)).await?.into()) } - async fn to_string(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { - tracing::debug!("java.lang.Integer::toString({this:?})"); + async fn value_of_string_radix(jvm: &Jvm, _: &mut RuntimeContext, value: ClassInstanceRef, radix: i32) -> Result> { + if value.is_null() { + return Err(jvm.exception("java/lang/NumberFormatException", "null").await); + } + let value = JavaLangString::to_rust_string(jvm, &value).await?; + let value = Self::parse_value(jvm, &value, radix as u32).await?; + Ok(jvm.new_class("java/lang/Integer", "(I)V", (value,)).await?.into()) + } - let value: i32 = jvm.get_field(&this, "value", "I").await?; + async fn decode(jvm: &Jvm, _: &mut RuntimeContext, value: ClassInstanceRef) -> Result> { + if value.is_null() { + return Err(jvm.exception("java/lang/NumberFormatException", "null").await); + } + let value = JavaLangString::to_rust_string(jvm, &value).await?; + let (sign, body) = if let Some(body) = value.strip_prefix('-') { + ("-", body) + } else if let Some(body) = value.strip_prefix('+') { + ("+", body) + } else { + ("", value.as_str()) + }; + let (radix, body) = if let Some(body) = body + .strip_prefix("0x") + .or_else(|| body.strip_prefix("0X")) + .or_else(|| body.strip_prefix('#')) + { + (16, body) + } else if body.starts_with('0') && body.len() > 1 { + (8, &body[1..]) + } else { + (10, body) + }; + if body.starts_with('-') || body.starts_with('+') { + return Err(jvm.exception("java/lang/NumberFormatException", "Sign character in wrong position").await); + } + let mut signed = RustString::from(sign); + signed.push_str(body); + let parsed = Self::parse_value(jvm, &signed, radix).await?; + Ok(jvm.new_class("java/lang/Integer", "(I)V", (parsed,)).await?.into()) + } - let string = JavaLangString::from_rust_string(jvm, &value.to_string()).await?; + async fn get_integer(jvm: &Jvm, _: &mut RuntimeContext, key: ClassInstanceRef) -> Result> { + let default: ClassInstanceRef = None.into(); + jvm.invoke_static( + "java/lang/Integer", + "getInteger", + "(Ljava/lang/String;Ljava/lang/Integer;)Ljava/lang/Integer;", + (key, default), + ) + .await + } - Ok(string.into()) + async fn get_integer_value_default( + jvm: &Jvm, + _: &mut RuntimeContext, + key: ClassInstanceRef, + default: i32, + ) -> Result> { + let default = jvm.new_class("java/lang/Integer", "(I)V", (default,)).await?; + jvm.invoke_static( + "java/lang/Integer", + "getInteger", + "(Ljava/lang/String;Ljava/lang/Integer;)Ljava/lang/Integer;", + (key, default), + ) + .await } - async fn to_string_static(jvm: &Jvm, _: &mut RuntimeContext, value: i32) -> Result> { - tracing::debug!("java.lang.Integer::toString({value:?})"); + async fn get_integer_object_default( + jvm: &Jvm, + _: &mut RuntimeContext, + key: ClassInstanceRef, + default: ClassInstanceRef, + ) -> Result> { + if key.is_null() { + return Ok(default); + } + let value: ClassInstanceRef = jvm + .invoke_static("java/lang/System", "getProperty", "(Ljava/lang/String;)Ljava/lang/String;", (key,)) + .await?; + if value.is_null() { + return Ok(default); + } + match jvm + .invoke_static("java/lang/Integer", "decode", "(Ljava/lang/String;)Ljava/lang/Integer;", (value,)) + .await + { + Ok(value) => Ok(value), + Err(JavaError::JavaException(exception)) if jvm.is_instance(&*exception, "java/lang/NumberFormatException") => Ok(default), + Err(error) => Err(error), + } + } - let string = JavaLangString::from_rust_string(jvm, &value.to_string()).await?; + async fn int_value(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + jvm.get_field(&this, "value", "I").await + } - Ok(string.into()) + async fn long_value(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let value: i32 = jvm.invoke_virtual(&this, "intValue", "()I", ()).await?; + Ok(value as i64) } - async fn parse_int(jvm: &Jvm, _: &mut RuntimeContext, s: ClassInstanceRef) -> Result { - tracing::debug!("java.lang.Integer::parseInt({s:?})"); + async fn float_value(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let value: i32 = jvm.invoke_virtual(&this, "intValue", "()I", ()).await?; + Ok(value as f32) + } - let s = JavaLangString::to_rust_string(jvm, &s).await?; + async fn double_value(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let value: i32 = jvm.invoke_virtual(&this, "intValue", "()I", ()).await?; + Ok(value as f64) + } - match s.parse() { - Ok(x) => Ok(x), - Err(_) => Err(jvm - .exception("java/lang/NumberFormatException", &format!("For input string: \"{s}\"")) - .await), + fn format_value(value: i64, radix: u32) -> RustString { + let radix = if (2..=36).contains(&radix) { radix } else { 10 }; + if value == 0 { + return "0".into(); + } + let negative = value < 0; + let mut magnitude = value.unsigned_abs(); + let mut result = RustString::new(); + while magnitude != 0 { + let digit = (magnitude % u64::from(radix)) as u32; + result.push(char::from_digit(digit, radix).unwrap_or('0')); + magnitude /= u64::from(radix); + } + if negative { + result.push('-'); } + result.chars().rev().collect() + } + + async fn to_string(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let value: i32 = jvm.invoke_virtual(&this, "intValue", "()I", ()).await?; + Ok(JavaLangString::from_rust_string(jvm, &Self::format_value(value as i64, 10)).await?.into()) + } + + async fn to_string_static(jvm: &Jvm, _: &mut RuntimeContext, value: i32) -> Result> { + Ok(JavaLangString::from_rust_string(jvm, &Self::format_value(value as i64, 10)).await?.into()) + } + + async fn to_string_radix_static(jvm: &Jvm, _: &mut RuntimeContext, value: i32, radix: i32) -> Result> { + Ok(JavaLangString::from_rust_string(jvm, &Self::format_value(value as i64, radix as u32)) + .await? + .into()) + } + + async fn to_binary_string(jvm: &Jvm, _: &mut RuntimeContext, value: i32) -> Result> { + let text = if value < 0 { + format!("{:032b}", value as u32) + } else { + Self::format_value(value as i64, 2) + }; + Ok(JavaLangString::from_rust_string(jvm, &text).await?.into()) + } + + async fn to_octal_string(jvm: &Jvm, _: &mut RuntimeContext, value: i32) -> Result> { + let text = if value < 0 { + format!("{:011o}", value as u32) + } else { + Self::format_value(value as i64, 8) + }; + Ok(JavaLangString::from_rust_string(jvm, &text).await?.into()) } async fn to_hex_string(jvm: &Jvm, _: &mut RuntimeContext, value: i32) -> Result> { - tracing::debug!("java.lang.Integer::toHexString({value:?})"); + let text = if value < 0 { + format!("{:08x}", value as u32) + } else { + Self::format_value(value as i64, 16) + }; + Ok(JavaLangString::from_rust_string(jvm, &text).await?.into()) + } - let string = JavaLangString::from_rust_string(jvm, &format!("{value:x}")).await?; + async fn hash_code(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + jvm.get_field(&this, "value", "I").await + } - Ok(string.into()) + async fn equals(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, other: ClassInstanceRef) -> Result { + if other.is_null() || !jvm.is_instance(&**other, "java/lang/Integer") { + return Ok(false); + } + let this_value: i32 = jvm.invoke_virtual(&this, "intValue", "()I", ()).await?; + let other_value: i32 = jvm.invoke_virtual(&other, "intValue", "()I", ()).await?; + Ok(this_value == other_value) + } + + async fn compare_to(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, other: ClassInstanceRef) -> Result { + if other.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "other").await); + } + let this_value: i32 = jvm.invoke_virtual(&this, "intValue", "()I", ()).await?; + let other_value: i32 = jvm.invoke_virtual(&other, "intValue", "()I", ()).await?; + Ok(this_value.cmp(&other_value) as i32) + } + + async fn compare_to_object(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, other: ClassInstanceRef) -> Result { + if other.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "other").await); + } + if !jvm.is_instance(&**other, "java/lang/Integer") { + return Err(jvm.exception("java/lang/ClassCastException", "java/lang/Object is not Integer").await); + } + let other = ClassInstanceRef::::from(other.instance); + let this_value: i32 = jvm.invoke_virtual(&this, "intValue", "()I", ()).await?; + let other_value: i32 = jvm.invoke_virtual(&other, "intValue", "()I", ()).await?; + Ok(this_value.cmp(&other_value) as i32) } } diff --git a/java_runtime/src/classes/java/lang/long.rs b/java_runtime/src/classes/java/lang/long.rs new file mode 100644 index 00000000..7c0b1539 --- /dev/null +++ b/java_runtime/src/classes/java/lang/long.rs @@ -0,0 +1,413 @@ +use alloc::{format, string::String as RustString, vec}; + +use java_class_proto::{JavaFieldProto, JavaMethodProto}; +use java_constants::{ClassAccessFlags, FieldAccessFlags, MethodAccessFlags}; +use jvm::{ + ClassInstanceRef, JavaChar, JavaError, Jvm, Result, + runtime::{JavaLangClass, JavaLangString}, +}; + +use crate::{ + RuntimeClassProto, RuntimeContext, + classes::java::lang::{Character, Object, String}, +}; + +// public final class java.lang.Long +pub struct Long; + +impl Long { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/lang/Long", + parent_class: Some("java/lang/Number"), + interfaces: vec!["java/lang/Comparable"], + methods: vec![ + JavaMethodProto::new("", "()V", Self::clinit, MethodAccessFlags::STATIC), + JavaMethodProto::new("", "(J)V", Self::init, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("", "(Ljava/lang/String;)V", Self::init_string, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "parseLong", + "(Ljava/lang/String;)J", + Self::parse_long, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "parseLong", + "(Ljava/lang/String;I)J", + Self::parse_long_radix, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "valueOf", + "(Ljava/lang/String;)Ljava/lang/Long;", + Self::value_of_string, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "valueOf", + "(Ljava/lang/String;I)Ljava/lang/Long;", + Self::value_of_string_radix, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "decode", + "(Ljava/lang/String;)Ljava/lang/Long;", + Self::decode, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "getLong", + "(Ljava/lang/String;)Ljava/lang/Long;", + Self::get_long, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "getLong", + "(Ljava/lang/String;J)Ljava/lang/Long;", + Self::get_long_value_default, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "getLong", + "(Ljava/lang/String;Ljava/lang/Long;)Ljava/lang/Long;", + Self::get_long_object_default, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new("byteValue", "()B", Self::byte_value, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("shortValue", "()S", Self::short_value, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("intValue", "()I", Self::int_value, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("longValue", "()J", Self::long_value, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("floatValue", "()F", Self::float_value, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("doubleValue", "()D", Self::double_value, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("toString", "()Ljava/lang/String;", Self::to_string, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "toString", + "(J)Ljava/lang/String;", + Self::to_string_static, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "toString", + "(JI)Ljava/lang/String;", + Self::to_string_radix, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "toBinaryString", + "(J)Ljava/lang/String;", + Self::to_binary_string, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "toOctalString", + "(J)Ljava/lang/String;", + Self::to_octal_string, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "toHexString", + "(J)Ljava/lang/String;", + Self::to_hex_string, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new("hashCode", "()I", Self::hash_code, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("equals", "(Ljava/lang/Object;)Z", Self::equals, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("compareTo", "(Ljava/lang/Long;)I", Self::compare_to, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("compareTo", "(Ljava/lang/Object;)I", Self::compare_to_object, MethodAccessFlags::PUBLIC), + ], + fields: vec![ + JavaFieldProto::new( + "MIN_VALUE", + "J", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "MAX_VALUE", + "J", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "TYPE", + "Ljava/lang/Class;", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new("value", "J", FieldAccessFlags::PRIVATE | FieldAccessFlags::FINAL), + ], + access_flags: ClassAccessFlags::PUBLIC | ClassAccessFlags::FINAL, + } + } + + async fn clinit(jvm: &Jvm, _: &mut RuntimeContext) -> Result<()> { + jvm.put_static_field("java/lang/Long", "MIN_VALUE", "J", i64::MIN).await?; + jvm.put_static_field("java/lang/Long", "MAX_VALUE", "J", i64::MAX).await?; + jvm.put_static_field( + "java/lang/Long", + "TYPE", + "Ljava/lang/Class;", + JavaLangClass::from_rust_primitive(jvm, "long").await?, + ) + .await + } + async fn init(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, value: i64) -> Result<()> { + let _: () = jvm.invoke_special(&this, "java/lang/Number", "", "()V", ()).await?; + jvm.put_field(&mut this, "value", "J", value).await + } + async fn init_string(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, value: ClassInstanceRef) -> Result<()> { + let value: i64 = jvm + .invoke_static("java/lang/Long", "parseLong", "(Ljava/lang/String;)J", (value,)) + .await?; + let mut this = this; + let _: () = jvm.invoke_special(&this, "java/lang/Number", "", "()V", ()).await?; + jvm.put_field(&mut this, "value", "J", value).await + } + fn parse_raw(value: &str, radix: u32) -> Option { + if !(2..=36).contains(&radix) || value.is_empty() { + return None; + } + let negative = value.starts_with('-'); + let body = value.strip_prefix('-').or_else(|| value.strip_prefix('+')).unwrap_or(value); + if body.is_empty() { + return None; + } + let limit = if negative { i128::from(i64::MIN) } else { -i128::from(i64::MAX) }; + let mut result = 0i128; + for value in body.chars() { + let value = JavaChar::try_from(u32::from(value)).ok()?; + let digit = i128::from(Character::digit_value(value, radix as i32)); + if digit < 0 { + return None; + } + if result < (limit + digit) / i128::from(radix) { + return None; + } + result = result * i128::from(radix) - digit; + } + if !negative && result == i128::from(i64::MIN) { + return None; + } + Some(if negative { result as i64 } else { -result as i64 }) + } + async fn parse_long(jvm: &Jvm, _: &mut RuntimeContext, value: ClassInstanceRef) -> Result { + if value.is_null() { + return Err(jvm.exception("java/lang/NumberFormatException", "null").await); + } + let value = JavaLangString::to_rust_string(jvm, &value).await?; + match Self::parse_raw(&value, 10) { + Some(value) => Ok(value), + None => Err(jvm + .exception("java/lang/NumberFormatException", &format!("For input string: \"{value}\"")) + .await), + } + } + async fn parse_long_radix(jvm: &Jvm, _: &mut RuntimeContext, value: ClassInstanceRef, radix: i32) -> Result { + if value.is_null() { + return Err(jvm.exception("java/lang/NumberFormatException", "null").await); + } + let value = JavaLangString::to_rust_string(jvm, &value).await?; + match Self::parse_raw(&value, radix as u32) { + Some(value) => Ok(value), + None => Err(jvm + .exception("java/lang/NumberFormatException", &format!("For input string: \"{value}\"")) + .await), + } + } + async fn value_of_string(jvm: &Jvm, _: &mut RuntimeContext, value: ClassInstanceRef) -> Result> { + let value: i64 = jvm + .invoke_static("java/lang/Long", "parseLong", "(Ljava/lang/String;)J", (value,)) + .await?; + Ok(jvm.new_class("java/lang/Long", "(J)V", (value,)).await?.into()) + } + async fn value_of_string_radix(jvm: &Jvm, _: &mut RuntimeContext, value: ClassInstanceRef, radix: i32) -> Result> { + let value: i64 = jvm + .invoke_static("java/lang/Long", "parseLong", "(Ljava/lang/String;I)J", (value, radix)) + .await?; + Ok(jvm.new_class("java/lang/Long", "(J)V", (value,)).await?.into()) + } + async fn decode(jvm: &Jvm, _: &mut RuntimeContext, value: ClassInstanceRef) -> Result> { + if value.is_null() { + return Err(jvm.exception("java/lang/NumberFormatException", "null").await); + } + let value = JavaLangString::to_rust_string(jvm, &value).await?; + let (sign, body) = if let Some(body) = value.strip_prefix('-') { + ("-", body) + } else if let Some(body) = value.strip_prefix('+') { + ("+", body) + } else { + ("", value.as_str()) + }; + let (radix, body) = if let Some(body) = body + .strip_prefix("0x") + .or_else(|| body.strip_prefix("0X")) + .or_else(|| body.strip_prefix('#')) + { + (16, body) + } else if body.starts_with('0') && body.len() > 1 { + (8, &body[1..]) + } else { + (10, body) + }; + if body.starts_with('-') || body.starts_with('+') { + return Err(jvm.exception("java/lang/NumberFormatException", "Sign character in wrong position").await); + } + let mut signed = RustString::from(sign); + signed.push_str(body); + let value = match Self::parse_raw(&signed, radix) { + Some(value) => value, + None => return Err(jvm.exception("java/lang/NumberFormatException", "invalid long").await), + }; + Ok(jvm.new_class("java/lang/Long", "(J)V", (value,)).await?.into()) + } + async fn get_long(jvm: &Jvm, _: &mut RuntimeContext, key: ClassInstanceRef) -> Result> { + let default: ClassInstanceRef = None.into(); + jvm.invoke_static( + "java/lang/Long", + "getLong", + "(Ljava/lang/String;Ljava/lang/Long;)Ljava/lang/Long;", + (key, default), + ) + .await + } + async fn get_long_value_default( + jvm: &Jvm, + _: &mut RuntimeContext, + key: ClassInstanceRef, + default: i64, + ) -> Result> { + let default = jvm.new_class("java/lang/Long", "(J)V", (default,)).await?; + jvm.invoke_static( + "java/lang/Long", + "getLong", + "(Ljava/lang/String;Ljava/lang/Long;)Ljava/lang/Long;", + (key, default), + ) + .await + } + async fn get_long_object_default( + jvm: &Jvm, + _: &mut RuntimeContext, + key: ClassInstanceRef, + default: ClassInstanceRef, + ) -> Result> { + if key.is_null() { + return Ok(default); + } + let value: ClassInstanceRef = jvm + .invoke_static("java/lang/System", "getProperty", "(Ljava/lang/String;)Ljava/lang/String;", (key,)) + .await?; + if value.is_null() { + return Ok(default); + } + match jvm + .invoke_static("java/lang/Long", "decode", "(Ljava/lang/String;)Ljava/lang/Long;", (value,)) + .await + { + Ok(value) => Ok(value), + Err(JavaError::JavaException(exception)) if jvm.is_instance(&*exception, "java/lang/NumberFormatException") => Ok(default), + Err(error) => Err(error), + } + } + async fn byte_value(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + Ok(jvm.get_field::(&this, "value", "J").await? as i8) + } + async fn short_value(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + Ok(jvm.get_field::(&this, "value", "J").await? as i16) + } + async fn int_value(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + Ok(jvm.get_field::(&this, "value", "J").await? as i32) + } + async fn long_value(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + jvm.get_field(&this, "value", "J").await + } + async fn float_value(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + Ok(jvm.get_field::(&this, "value", "J").await? as f32) + } + async fn double_value(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + Ok(jvm.get_field::(&this, "value", "J").await? as f64) + } + fn format_value(value: i64, radix: u32) -> RustString { + let radix = if (2..=36).contains(&radix) { radix } else { 10 }; + if value == 0 { + return "0".into(); + } + let negative = value < 0; + let mut magnitude = value.unsigned_abs(); + let mut result = RustString::new(); + while magnitude != 0 { + result.push(char::from_digit((magnitude % u64::from(radix)) as u32, radix).unwrap_or('0')); + magnitude /= u64::from(radix); + } + if negative { + result.push('-'); + } + result.chars().rev().collect() + } + async fn to_string(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let value: i64 = jvm.invoke_virtual(&this, "longValue", "()J", ()).await?; + Ok(JavaLangString::from_rust_string(jvm, &Self::format_value(value, 10)).await?.into()) + } + async fn to_string_static(jvm: &Jvm, _: &mut RuntimeContext, value: i64) -> Result> { + Ok(JavaLangString::from_rust_string(jvm, &Self::format_value(value, 10)).await?.into()) + } + async fn to_string_radix(jvm: &Jvm, _: &mut RuntimeContext, value: i64, radix: i32) -> Result> { + Ok(JavaLangString::from_rust_string(jvm, &Self::format_value(value, radix as u32)) + .await? + .into()) + } + async fn to_binary_string(jvm: &Jvm, _: &mut RuntimeContext, value: i64) -> Result> { + let text = if value < 0 { + format!("{:064b}", value as u64) + } else { + Self::format_value(value, 2) + }; + Ok(JavaLangString::from_rust_string(jvm, &text).await?.into()) + } + async fn to_octal_string(jvm: &Jvm, _: &mut RuntimeContext, value: i64) -> Result> { + let text = if value < 0 { + format!("{:022o}", value as u64) + } else { + Self::format_value(value, 8) + }; + Ok(JavaLangString::from_rust_string(jvm, &text).await?.into()) + } + async fn to_hex_string(jvm: &Jvm, _: &mut RuntimeContext, value: i64) -> Result> { + let text = if value < 0 { + format!("{:016x}", value as u64) + } else { + Self::format_value(value, 16) + }; + Ok(JavaLangString::from_rust_string(jvm, &text).await?.into()) + } + async fn hash_code(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let value: i64 = jvm.invoke_virtual(&this, "longValue", "()J", ()).await?; + Ok((value ^ (value >> 32)) as i32) + } + async fn equals(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, other: ClassInstanceRef) -> Result { + if other.is_null() || !jvm.is_instance(&**other, "java/lang/Long") { + return Ok(false); + } + Ok( + jvm.invoke_virtual::<_, i64>(&this, "longValue", "()J", ()).await? + == jvm.invoke_virtual::<_, i64>(&other, "longValue", "()J", ()).await?, + ) + } + async fn compare_to(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, other: ClassInstanceRef) -> Result { + if other.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "other").await); + } + let left: i64 = jvm.invoke_virtual(&this, "longValue", "()J", ()).await?; + let right: i64 = jvm.invoke_virtual(&other, "longValue", "()J", ()).await?; + Ok(left.cmp(&right) as i32) + } + async fn compare_to_object(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, other: ClassInstanceRef) -> Result { + if other.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "other").await); + } + if !jvm.is_instance(&**other, "java/lang/Long") { + return Err(jvm.exception("java/lang/ClassCastException", "not Long").await); + } + let other = ClassInstanceRef::::from(other.instance); + let left: i64 = jvm.invoke_virtual(&this, "longValue", "()J", ()).await?; + let right: i64 = jvm.invoke_virtual(&other, "longValue", "()J", ()).await?; + Ok(left.cmp(&right) as i32) + } +} diff --git a/java_runtime/src/classes/java/lang/number.rs b/java_runtime/src/classes/java/lang/number.rs new file mode 100644 index 00000000..28229567 --- /dev/null +++ b/java_runtime/src/classes/java/lang/number.rs @@ -0,0 +1,45 @@ +use alloc::vec; + +use java_class_proto::JavaMethodProto; +use java_constants::{ClassAccessFlags, MethodAccessFlags}; +use jvm::{ClassInstanceRef, Jvm, Result}; + +use crate::{RuntimeClassProto, RuntimeContext}; + +// abstract class java.lang.Number +pub struct Number; + +impl Number { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/lang/Number", + parent_class: Some("java/lang/Object"), + interfaces: vec!["java/io/Serializable"], + methods: vec![ + JavaMethodProto::new("", "()V", Self::init, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("byteValue", "()B", Self::byte_value, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("shortValue", "()S", Self::short_value, MethodAccessFlags::PUBLIC), + JavaMethodProto::new_abstract("intValue", "()I", MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT), + JavaMethodProto::new_abstract("longValue", "()J", MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT), + JavaMethodProto::new_abstract("floatValue", "()F", MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT), + JavaMethodProto::new_abstract("doubleValue", "()D", MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT), + ], + fields: vec![], + access_flags: ClassAccessFlags::PUBLIC | ClassAccessFlags::ABSTRACT, + } + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result<()> { + jvm.invoke_special(&this, "java/lang/Object", "", "()V", ()).await + } + + async fn byte_value(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let value: i32 = jvm.invoke_virtual(&this, "intValue", "()I", ()).await?; + Ok(value as i8) + } + + async fn short_value(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + let value: i32 = jvm.invoke_virtual(&this, "intValue", "()I", ()).await?; + Ok(value as i16) + } +} diff --git a/java_runtime/src/classes/java/lang/short.rs b/java_runtime/src/classes/java/lang/short.rs new file mode 100644 index 00000000..21637c8a --- /dev/null +++ b/java_runtime/src/classes/java/lang/short.rs @@ -0,0 +1,225 @@ +use alloc::{format, vec}; + +use java_class_proto::{JavaFieldProto, JavaMethodProto}; +use java_constants::{ClassAccessFlags, FieldAccessFlags, MethodAccessFlags}; +use jvm::{ + ClassInstanceRef, Jvm, Result, + runtime::{JavaLangClass, JavaLangString}, +}; + +use crate::{ + RuntimeClassProto, RuntimeContext, + classes::java::lang::{Object, String}, +}; + +// public final class java.lang.Short +pub struct Short; + +impl Short { + pub fn as_proto() -> RuntimeClassProto { + RuntimeClassProto { + name: "java/lang/Short", + parent_class: Some("java/lang/Number"), + interfaces: vec!["java/lang/Comparable"], + methods: vec![ + JavaMethodProto::new("", "()V", Self::clinit, MethodAccessFlags::STATIC), + JavaMethodProto::new("", "(S)V", Self::init, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("", "(Ljava/lang/String;)V", Self::init_string, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "parseShort", + "(Ljava/lang/String;)S", + Self::parse_short, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "parseShort", + "(Ljava/lang/String;I)S", + Self::parse_short_radix, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "valueOf", + "(Ljava/lang/String;)Ljava/lang/Short;", + Self::value_of_string, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "valueOf", + "(Ljava/lang/String;I)Ljava/lang/Short;", + Self::value_of_string_radix, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new( + "decode", + "(Ljava/lang/String;)Ljava/lang/Short;", + Self::decode, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new("byteValue", "()B", Self::byte_value, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("shortValue", "()S", Self::short_value, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("intValue", "()I", Self::int_value, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("longValue", "()J", Self::long_value, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("floatValue", "()F", Self::float_value, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("doubleValue", "()D", Self::double_value, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("toString", "()Ljava/lang/String;", Self::to_string, MethodAccessFlags::PUBLIC), + JavaMethodProto::new( + "toString", + "(S)Ljava/lang/String;", + Self::to_string_static, + MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC, + ), + JavaMethodProto::new("hashCode", "()I", Self::hash_code, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("equals", "(Ljava/lang/Object;)Z", Self::equals, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("compareTo", "(Ljava/lang/Short;)I", Self::compare_to, MethodAccessFlags::PUBLIC), + JavaMethodProto::new("compareTo", "(Ljava/lang/Object;)I", Self::compare_to_object, MethodAccessFlags::PUBLIC), + ], + fields: vec![ + JavaFieldProto::new( + "MIN_VALUE", + "S", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "MAX_VALUE", + "S", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new( + "TYPE", + "Ljava/lang/Class;", + FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL, + ), + JavaFieldProto::new("value", "S", FieldAccessFlags::PRIVATE | FieldAccessFlags::FINAL), + ], + access_flags: ClassAccessFlags::PUBLIC | ClassAccessFlags::FINAL, + } + } + + async fn clinit(jvm: &Jvm, _: &mut RuntimeContext) -> Result<()> { + jvm.put_static_field("java/lang/Short", "MIN_VALUE", "S", i16::MIN).await?; + jvm.put_static_field("java/lang/Short", "MAX_VALUE", "S", i16::MAX).await?; + jvm.put_static_field( + "java/lang/Short", + "TYPE", + "Ljava/lang/Class;", + JavaLangClass::from_rust_primitive(jvm, "short").await?, + ) + .await + } + + async fn init(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, value: i16) -> Result<()> { + let _: () = jvm.invoke_special(&this, "java/lang/Number", "", "()V", ()).await?; + jvm.put_field(&mut this, "value", "S", value).await + } + + async fn init_string(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, value: ClassInstanceRef) -> Result<()> { + let value: i16 = jvm + .invoke_static("java/lang/Short", "parseShort", "(Ljava/lang/String;)S", (value,)) + .await?; + let mut this = this; + let _: () = jvm.invoke_special(&this, "java/lang/Number", "", "()V", ()).await?; + jvm.put_field(&mut this, "value", "S", value).await + } + + async fn parse_short(jvm: &Jvm, _: &mut RuntimeContext, value: ClassInstanceRef) -> Result { + let parsed: i32 = jvm + .invoke_static("java/lang/Integer", "parseInt", "(Ljava/lang/String;)I", (value,)) + .await?; + if !(i32::from(i16::MIN)..=i32::from(i16::MAX)).contains(&parsed) { + return Err(jvm.exception("java/lang/NumberFormatException", "Value out of range").await); + } + Ok(parsed as i16) + } + + async fn parse_short_radix(jvm: &Jvm, _: &mut RuntimeContext, value: ClassInstanceRef, radix: i32) -> Result { + let parsed: i32 = jvm + .invoke_static("java/lang/Integer", "parseInt", "(Ljava/lang/String;I)I", (value, radix)) + .await?; + if !(i32::from(i16::MIN)..=i32::from(i16::MAX)).contains(&parsed) { + return Err(jvm.exception("java/lang/NumberFormatException", "Value out of range").await); + } + Ok(parsed as i16) + } + + async fn value_of_string(jvm: &Jvm, _: &mut RuntimeContext, value: ClassInstanceRef) -> Result> { + let value: i16 = jvm + .invoke_static("java/lang/Short", "parseShort", "(Ljava/lang/String;)S", (value,)) + .await?; + Ok(jvm.new_class("java/lang/Short", "(S)V", (value,)).await?.into()) + } + + async fn value_of_string_radix(jvm: &Jvm, _: &mut RuntimeContext, value: ClassInstanceRef, radix: i32) -> Result> { + let value: i16 = jvm + .invoke_static("java/lang/Short", "parseShort", "(Ljava/lang/String;I)S", (value, radix)) + .await?; + Ok(jvm.new_class("java/lang/Short", "(S)V", (value,)).await?.into()) + } + + async fn decode(jvm: &Jvm, _: &mut RuntimeContext, value: ClassInstanceRef) -> Result> { + let integer = jvm + .invoke_static("java/lang/Integer", "decode", "(Ljava/lang/String;)Ljava/lang/Integer;", (value,)) + .await?; + let value: i32 = jvm.invoke_virtual(&integer, "intValue", "()I", ()).await?; + if !(i32::from(i16::MIN)..=i32::from(i16::MAX)).contains(&value) { + return Err(jvm.exception("java/lang/NumberFormatException", "Value out of range").await); + } + Ok(jvm.new_class("java/lang/Short", "(S)V", (value as i16,)).await?.into()) + } + + async fn byte_value(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + Ok((jvm.get_field::(&this, "value", "S").await?) as i8) + } + async fn short_value(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + jvm.get_field(&this, "value", "S").await + } + async fn int_value(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + Ok(jvm.get_field::(&this, "value", "S").await? as i32) + } + async fn long_value(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + Ok(jvm.get_field::(&this, "value", "S").await? as i64) + } + async fn float_value(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + Ok(jvm.get_field::(&this, "value", "S").await? as f32) + } + async fn double_value(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + Ok(jvm.get_field::(&this, "value", "S").await? as f64) + } + + async fn to_string(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { + let value: i16 = jvm.invoke_virtual(&this, "shortValue", "()S", ()).await?; + Ok(JavaLangString::from_rust_string(jvm, &format!("{value}")).await?.into()) + } + async fn to_string_static(jvm: &Jvm, _: &mut RuntimeContext, value: i16) -> Result> { + Ok(JavaLangString::from_rust_string(jvm, &format!("{value}")).await?.into()) + } + async fn hash_code(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + Ok(jvm.get_field::(&this, "value", "S").await? as i32) + } + async fn equals(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, other: ClassInstanceRef) -> Result { + if other.is_null() || !jvm.is_instance(&**other, "java/lang/Short") { + return Ok(false); + } + Ok(jvm.invoke_virtual::<_, i16>(&this, "shortValue", "()S", ()).await? + == jvm.invoke_virtual::<_, i16>(&other, "shortValue", "()S", ()).await?) + } + async fn compare_to(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, other: ClassInstanceRef) -> Result { + if other.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "other").await); + } + let left: i16 = jvm.invoke_virtual(&this, "shortValue", "()S", ()).await?; + let right: i16 = jvm.invoke_virtual(&other, "shortValue", "()S", ()).await?; + Ok(left.cmp(&right) as i32) + } + async fn compare_to_object(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, other: ClassInstanceRef) -> Result { + if other.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "other").await); + } + if !jvm.is_instance(&**other, "java/lang/Short") { + return Err(jvm.exception("java/lang/ClassCastException", "not Short").await); + } + let other = ClassInstanceRef::::from(other.instance); + let left: i16 = jvm.invoke_virtual(&this, "shortValue", "()S", ()).await?; + let right: i16 = jvm.invoke_virtual(&other, "shortValue", "()S", ()).await?; + Ok(left.cmp(&right) as i32) + } +} diff --git a/java_runtime/src/loader.rs b/java_runtime/src/loader.rs index cbd2fced..0fe3fa8f 100644 --- a/java_runtime/src/loader.rs +++ b/java_runtime/src/loader.rs @@ -36,16 +36,21 @@ pub fn get_runtime_class_proto(name: &str) -> Option { crate::classes::java::lang::ArithmeticException::as_proto(), crate::classes::java::lang::ArrayIndexOutOfBoundsException::as_proto(), crate::classes::java::lang::ArrayStoreException::as_proto(), + crate::classes::java::lang::Boolean::as_proto(), crate::classes::java::lang::Class::as_proto(), crate::classes::java::lang::ClassCastException::as_proto(), crate::classes::java::lang::ClassLoader::as_proto(), crate::classes::java::lang::ClassNotFoundException::as_proto(), + crate::classes::java::lang::Byte::as_proto(), + crate::classes::java::lang::Character::as_proto(), crate::classes::java::lang::Cloneable::as_proto(), crate::classes::java::lang::CloneNotSupportedException::as_proto(), crate::classes::java::lang::Comparable::as_proto(), + crate::classes::java::lang::Double::as_proto(), crate::classes::java::lang::Error::as_proto(), crate::classes::java::lang::Exception::as_proto(), crate::classes::java::lang::ExceptionInInitializerError::as_proto(), + crate::classes::java::lang::Float::as_proto(), crate::classes::java::lang::IllegalArgumentException::as_proto(), crate::classes::java::lang::InstantiationError::as_proto(), crate::classes::java::lang::IncompatibleClassChangeError::as_proto(), @@ -53,6 +58,7 @@ pub fn get_runtime_class_proto(name: &str) -> Option { crate::classes::java::lang::Integer::as_proto(), crate::classes::java::lang::InterruptedException::as_proto(), crate::classes::java::lang::LinkageError::as_proto(), + crate::classes::java::lang::Long::as_proto(), crate::classes::java::lang::Math::as_proto(), crate::classes::java::lang::NegativeArraySizeException::as_proto(), crate::classes::java::lang::NoClassDefFoundError::as_proto(), @@ -60,11 +66,13 @@ pub fn get_runtime_class_proto(name: &str) -> Option { crate::classes::java::lang::NoSuchMethodError::as_proto(), crate::classes::java::lang::NullPointerException::as_proto(), crate::classes::java::lang::NumberFormatException::as_proto(), + crate::classes::java::lang::Number::as_proto(), crate::classes::java::lang::Object::as_proto(), crate::classes::java::lang::Runnable::as_proto(), crate::classes::java::lang::Runtime::as_proto(), crate::classes::java::lang::RuntimeException::as_proto(), crate::classes::java::lang::SecurityException::as_proto(), + crate::classes::java::lang::Short::as_proto(), crate::classes::java::lang::String::as_proto(), crate::classes::java::lang::StringBuffer::as_proto(), crate::classes::java::lang::StringIndexOutOfBoundsException::as_proto(), diff --git a/java_runtime/tests/classes/java/lang/mod.rs b/java_runtime/tests/classes/java/lang/mod.rs index 0ccbbcec..9ab8df55 100644 --- a/java_runtime/tests/classes/java/lang/mod.rs +++ b/java_runtime/tests/classes/java/lang/mod.rs @@ -1,7 +1,15 @@ +mod test_boolean; +mod test_byte; +mod test_character; mod test_class; +mod test_double; +mod test_float; mod test_integer; +mod test_long; mod test_math; +mod test_number; mod test_object; +mod test_short; mod test_string; mod test_string_buffer; mod test_thread; diff --git a/java_runtime/tests/classes/java/lang/test_boolean.rs b/java_runtime/tests/classes/java/lang/test_boolean.rs new file mode 100644 index 00000000..eda529ef --- /dev/null +++ b/java_runtime/tests/classes/java/lang/test_boolean.rs @@ -0,0 +1,136 @@ +use java_runtime::classes::java::lang::{Boolean, Object, String}; +use jvm::{ClassInstanceRef, JavaError, Result, runtime::JavaLangString}; + +use test_utils::test_jvm; + +#[tokio::test] +async fn test_boolean_string_constants_and_type() -> Result<()> { + let jvm = test_jvm().await?; + + let true_value: ClassInstanceRef = jvm.new_class("java/lang/Boolean", "(Z)V", (true,)).await?.into(); + assert!(jvm.invoke_virtual::<_, bool>(&true_value, "booleanValue", "()Z", ()).await?); + assert_eq!(jvm.invoke_virtual::<_, i32>(&true_value, "hashCode", "()I", ()).await?, 1231); + + let text: ClassInstanceRef = jvm.invoke_virtual(&true_value, "toString", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &text).await?, "true"); + + let mixed_case = JavaLangString::from_rust_string(&jvm, "TrUe").await?; + let parsed: ClassInstanceRef = jvm + .invoke_static("java/lang/Boolean", "valueOf", "(Ljava/lang/String;)Ljava/lang/Boolean;", (mixed_case,)) + .await?; + assert!(jvm.invoke_virtual::<_, bool>(&parsed, "booleanValue", "()Z", ()).await?); + + let padded = JavaLangString::from_rust_string(&jvm, " true").await?; + let parsed: ClassInstanceRef = jvm + .invoke_static("java/lang/Boolean", "valueOf", "(Ljava/lang/String;)Ljava/lang/Boolean;", (padded,)) + .await?; + assert!(!jvm.invoke_virtual::<_, bool>(&parsed, "booleanValue", "()Z", ()).await?); + + let null_string: ClassInstanceRef = None.into(); + let parsed: ClassInstanceRef = jvm + .invoke_static( + "java/lang/Boolean", + "valueOf", + "(Ljava/lang/String;)Ljava/lang/Boolean;", + (null_string.clone(),), + ) + .await?; + assert!(!jvm.invoke_virtual::<_, bool>(&parsed, "booleanValue", "()Z", ()).await?); + + let from_null: ClassInstanceRef = jvm.new_class("java/lang/Boolean", "(Ljava/lang/String;)V", (null_string,)).await?.into(); + assert!(!jvm.invoke_virtual::<_, bool>(&from_null, "booleanValue", "()Z", ()).await?); + + let true_constant: ClassInstanceRef = jvm.get_static_field("java/lang/Boolean", "TRUE", "Ljava/lang/Boolean;").await?; + let false_constant: ClassInstanceRef = jvm.get_static_field("java/lang/Boolean", "FALSE", "Ljava/lang/Boolean;").await?; + assert!(jvm.invoke_virtual::<_, bool>(&true_constant, "booleanValue", "()Z", ()).await?); + assert!(!jvm.invoke_virtual::<_, bool>(&false_constant, "booleanValue", "()Z", ()).await?); + assert_eq!(jvm.invoke_virtual::<_, i32>(&false_constant, "hashCode", "()I", ()).await?, 1237); + assert!( + jvm.invoke_virtual::<_, bool>(&true_constant, "equals", "(Ljava/lang/Object;)Z", (true_value,)) + .await? + ); + assert!( + !jvm.invoke_virtual::<_, bool>(&false_constant, "equals", "(Ljava/lang/Object;)Z", (None,)) + .await? + ); + let object = jvm.new_class("java/lang/Object", "()V", ()).await?; + assert!( + !jvm.invoke_virtual::<_, bool>(&false_constant, "equals", "(Ljava/lang/Object;)Z", (object,)) + .await? + ); + assert!(!jvm.is_instance(&**true_constant, "java/lang/Comparable")); + assert!(jvm.is_instance(&**true_constant, "java/io/Serializable")); + + let typ = jvm.get_static_field("java/lang/Boolean", "TYPE", "Ljava/lang/Class;").await?; + let name: ClassInstanceRef = jvm.invoke_virtual(&typ, "getName", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &name).await?, "boolean"); + assert!(jvm.invoke_virtual::<_, bool>(&typ, "isPrimitive", "()Z", ()).await?); + + Ok(()) +} + +#[tokio::test] +async fn test_boolean_property_and_primitive_value_of_exclusion() -> Result<()> { + let jvm = test_jvm().await?; + + let key = JavaLangString::from_rust_string(&jvm, "rustjava.boolean.test").await?; + let value = JavaLangString::from_rust_string(&jvm, "TRUE").await?; + let _: ClassInstanceRef = jvm + .invoke_static( + "java/lang/System", + "setProperty", + "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Object;", + (key.clone(), value), + ) + .await?; + assert!( + jvm.invoke_static::<_, bool>("java/lang/Boolean", "getBoolean", "(Ljava/lang/String;)Z", (key,)) + .await? + ); + + let missing = JavaLangString::from_rust_string(&jvm, "rustjava.boolean.missing").await?; + assert!( + !jvm.invoke_static::<_, bool>("java/lang/Boolean", "getBoolean", "(Ljava/lang/String;)Z", (missing,)) + .await? + ); + + assert!( + !jvm.invoke_static::<_, bool>("java/lang/Boolean", "getBoolean", "(Ljava/lang/String;)Z", (None,)) + .await? + ); + let false_key = JavaLangString::from_rust_string(&jvm, "rustjava.boolean.false").await?; + let false_value = JavaLangString::from_rust_string(&jvm, "not-true").await?; + let _: ClassInstanceRef = jvm + .invoke_static( + "java/lang/System", + "setProperty", + "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Object;", + (false_key.clone(), false_value), + ) + .await?; + assert!( + !jvm.invoke_static::<_, bool>("java/lang/Boolean", "getBoolean", "(Ljava/lang/String;)Z", (false_key,)) + .await? + ); + + let result: Result> = jvm.invoke_static("java/lang/Boolean", "valueOf", "(Z)Ljava/lang/Boolean;", (true,)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("Boolean.valueOf(boolean) must remain outside the Java 1.2 API"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NoSuchMethodError")); + + let left = jvm.new_class("java/lang/Boolean", "(Z)V", (false,)).await?; + let right = jvm.new_class("java/lang/Boolean", "(Z)V", (true,)).await?; + let result: Result = jvm.invoke_virtual(&left, "compareTo", "(Ljava/lang/Boolean;)I", (right.clone(),)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("Boolean typed compareTo must remain outside the Java 1.2 API"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NoSuchMethodError")); + let result: Result = jvm.invoke_virtual(&left, "compareTo", "(Ljava/lang/Object;)I", (right,)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("Boolean raw compareTo must remain outside the Java 1.2 API"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NoSuchMethodError")); + + Ok(()) +} diff --git a/java_runtime/tests/classes/java/lang/test_byte.rs b/java_runtime/tests/classes/java/lang/test_byte.rs new file mode 100644 index 00000000..b49458bc --- /dev/null +++ b/java_runtime/tests/classes/java/lang/test_byte.rs @@ -0,0 +1,111 @@ +use java_runtime::classes::java::lang::{Byte, String}; +use jvm::{ClassInstanceRef, JavaError, Result, runtime::JavaLangString}; + +use test_utils::test_jvm; + +#[tokio::test] +async fn test_byte_api() -> Result<()> { + let jvm = test_jvm().await?; + let value = jvm.new_class("java/lang/Byte", "(B)V", (-2i8,)).await?; + assert_eq!(jvm.invoke_virtual::<_, i8>(&value, "byteValue", "()B", ()).await?, -2); + assert_eq!(jvm.invoke_virtual::<_, i16>(&value, "shortValue", "()S", ()).await?, -2); + assert_eq!(jvm.invoke_virtual::<_, i32>(&value, "intValue", "()I", ()).await?, -2); + assert_eq!(jvm.invoke_virtual::<_, i64>(&value, "longValue", "()J", ()).await?, -2); + assert_eq!(jvm.invoke_virtual::<_, f32>(&value, "floatValue", "()F", ()).await?, -2.0); + assert_eq!(jvm.invoke_virtual::<_, f64>(&value, "doubleValue", "()D", ()).await?, -2.0); + let text: jvm::ClassInstanceRef = + jvm.invoke_virtual(&value, "toString", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &text).await?, "-2"); + + let min = jvm.get_static_field::("java/lang/Byte", "MIN_VALUE", "B").await?; + let max = jvm.get_static_field::("java/lang/Byte", "MAX_VALUE", "B").await?; + assert_eq!((min, max), (i8::MIN, i8::MAX)); + let typ = jvm.get_static_field("java/lang/Byte", "TYPE", "Ljava/lang/Class;").await?; + let name: jvm::ClassInstanceRef = + jvm.invoke_virtual(&typ, "getName", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &name).await?, "byte"); + assert!(jvm.invoke_virtual::<_, bool>(&typ, "isPrimitive", "()Z", ()).await?); + + let string = JavaLangString::from_rust_string(&jvm, "0x7f").await?; + let decoded = jvm + .invoke_static("java/lang/Byte", "decode", "(Ljava/lang/String;)Ljava/lang/Byte;", (string,)) + .await?; + assert_eq!(jvm.invoke_virtual::<_, i8>(&decoded, "byteValue", "()B", ()).await?, 127); + + let invalid = JavaLangString::from_rust_string(&jvm, "128").await?; + let result: Result> = jvm + .invoke_static("java/lang/Byte", "valueOf", "(Ljava/lang/String;)Ljava/lang/Byte;", (invalid,)) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("Byte.valueOf must reject overflow"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NumberFormatException")); + + let result: Result = jvm.invoke_virtual(&value, "compareTo", "(Ljava/lang/Byte;)I", (None,)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("Byte typed compare null must throw NPE"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + + for (text, radix, expected) in [ + ("-10000000", 2, i8::MIN), + ("1111111", 2, i8::MAX), + ("-128", 10, i8::MIN), + ("127", 10, i8::MAX), + ("-80", 16, i8::MIN), + ("7f", 16, i8::MAX), + ("-3k", 36, i8::MIN), + ("3j", 36, i8::MAX), + ] { + let text = JavaLangString::from_rust_string(&jvm, text).await?; + let parsed: i8 = jvm + .invoke_static("java/lang/Byte", "parseByte", "(Ljava/lang/String;I)B", (text, radix)) + .await?; + assert_eq!(parsed, expected); + } + for text in ["-129", "128"] { + let text = JavaLangString::from_rust_string(&jvm, text).await?; + let result: Result = jvm.invoke_static("java/lang/Byte", "parseByte", "(Ljava/lang/String;)B", (text,)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("Byte.parseByte must reject overflow"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NumberFormatException")); + } + + let equal = jvm.new_class("java/lang/Byte", "(B)V", (-2i8,)).await?; + assert!( + jvm.invoke_virtual::<_, bool>(&value, "equals", "(Ljava/lang/Object;)Z", (equal.clone(),)) + .await? + ); + assert_eq!(jvm.invoke_virtual::<_, i32>(&value, "hashCode", "()I", ()).await?, -2); + assert_eq!( + jvm.invoke_virtual::<_, i32>(&value, "compareTo", "(Ljava/lang/Object;)I", (equal,)) + .await?, + 0 + ); + let object = jvm.new_class("java/lang/Object", "()V", ()).await?; + let result: Result = jvm.invoke_virtual(&value, "compareTo", "(Ljava/lang/Object;)I", (object,)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("Byte raw compare must reject another type"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/ClassCastException")); + let result: Result = jvm.invoke_virtual(&value, "compareTo", "(Ljava/lang/Object;)I", (None,)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("Byte raw compare null must throw NPE"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + + for (text, expected) in [("#7f", 127i8), ("0177", 127i8)] { + let text = JavaLangString::from_rust_string(&jvm, text).await?; + let decoded: ClassInstanceRef = jvm + .invoke_static("java/lang/Byte", "decode", "(Ljava/lang/String;)Ljava/lang/Byte;", (text,)) + .await?; + assert_eq!(jvm.invoke_virtual::<_, i8>(&decoded, "byteValue", "()B", ()).await?, expected); + } + + let typ = jvm.get_static_field("java/lang/Byte", "TYPE", "Ljava/lang/Class;").await?; + let name: ClassInstanceRef = jvm.invoke_virtual(&typ, "getName", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &name).await?, "byte"); + assert!(jvm.invoke_virtual::<_, bool>(&typ, "isPrimitive", "()Z", ()).await?); + Ok(()) +} diff --git a/java_runtime/tests/classes/java/lang/test_character.rs b/java_runtime/tests/classes/java/lang/test_character.rs new file mode 100644 index 00000000..3318a4cf --- /dev/null +++ b/java_runtime/tests/classes/java/lang/test_character.rs @@ -0,0 +1,368 @@ +use java_runtime::classes::java::lang::{Character, String}; +use jvm::{Array, ClassInstanceRef, JavaChar, JavaError, Result, runtime::JavaLangString}; + +use test_utils::test_jvm; + +#[tokio::test] +async fn test_character_value_constants_and_type() -> Result<()> { + let jvm = test_jvm().await?; + + let value: ClassInstanceRef = jvm.new_class("java/lang/Character", "(C)V", ('A' as JavaChar,)).await?.into(); + assert_eq!(jvm.invoke_virtual::<_, JavaChar>(&value, "charValue", "()C", ()).await?, 'A' as JavaChar); + assert_eq!(jvm.invoke_virtual::<_, i32>(&value, "hashCode", "()I", ()).await?, 'A' as i32); + + let text: ClassInstanceRef = jvm.invoke_virtual(&value, "toString", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &text).await?, "A"); + + assert_eq!(jvm.get_static_field::("java/lang/Character", "MIN_VALUE", "C").await?, 0); + assert_eq!(jvm.get_static_field::("java/lang/Character", "MAX_VALUE", "C").await?, u16::MAX); + assert_eq!(jvm.get_static_field::("java/lang/Character", "MIN_RADIX", "I").await?, 2); + assert_eq!(jvm.get_static_field::("java/lang/Character", "MAX_RADIX", "I").await?, 36); + + for (name, expected) in [ + ("UNASSIGNED", 0i8), + ("UPPERCASE_LETTER", 1), + ("LOWERCASE_LETTER", 2), + ("TITLECASE_LETTER", 3), + ("MODIFIER_LETTER", 4), + ("OTHER_LETTER", 5), + ("NON_SPACING_MARK", 6), + ("ENCLOSING_MARK", 7), + ("COMBINING_SPACING_MARK", 8), + ("DECIMAL_DIGIT_NUMBER", 9), + ("LETTER_NUMBER", 10), + ("OTHER_NUMBER", 11), + ("SPACE_SEPARATOR", 12), + ("LINE_SEPARATOR", 13), + ("PARAGRAPH_SEPARATOR", 14), + ("CONTROL", 15), + ("FORMAT", 16), + ("PRIVATE_USE", 18), + ("SURROGATE", 19), + ("DASH_PUNCTUATION", 20), + ("START_PUNCTUATION", 21), + ("END_PUNCTUATION", 22), + ("CONNECTOR_PUNCTUATION", 23), + ("OTHER_PUNCTUATION", 24), + ("MATH_SYMBOL", 25), + ("CURRENCY_SYMBOL", 26), + ("MODIFIER_SYMBOL", 27), + ("OTHER_SYMBOL", 28), + ] { + assert_eq!(jvm.get_static_field::("java/lang/Character", name, "B").await?, expected); + } + + let typ = jvm.get_static_field("java/lang/Character", "TYPE", "Ljava/lang/Class;").await?; + let name: ClassInstanceRef = jvm.invoke_virtual(&typ, "getName", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &name).await?, "char"); + assert!(jvm.invoke_virtual::<_, bool>(&typ, "isPrimitive", "()Z", ()).await?); + assert!(jvm.is_instance(&**value, "java/lang/Comparable")); + assert!(jvm.is_instance(&**value, "java/io/Serializable")); + + let result: Result> = jvm + .invoke_static("java/lang/Character", "valueOf", "(C)Ljava/lang/Character;", ('A' as JavaChar,)) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("Character.valueOf(char) must remain outside the Java 1.2 API"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NoSuchMethodError")); + + Ok(()) +} + +#[tokio::test] +async fn test_character_ascii_non_ascii_and_radix() -> Result<()> { + let jvm = test_jvm().await?; + + assert!( + jvm.invoke_static::<_, bool>("java/lang/Character", "isLetter", "(C)Z", ('A' as JavaChar,)) + .await? + ); + assert!( + jvm.invoke_static::<_, bool>("java/lang/Character", "isUpperCase", "(C)Z", ('A' as JavaChar,)) + .await? + ); + assert!( + jvm.invoke_static::<_, bool>("java/lang/Character", "isLowerCase", "(C)Z", ('z' as JavaChar,)) + .await? + ); + assert!( + jvm.invoke_static::<_, bool>("java/lang/Character", "isDigit", "(C)Z", ('7' as JavaChar,)) + .await? + ); + assert!( + jvm.invoke_static::<_, bool>("java/lang/Character", "isJavaIdentifierStart", "(C)Z", ('_' as JavaChar,)) + .await? + ); + assert!( + jvm.invoke_static::<_, bool>("java/lang/Character", "isJavaIdentifierStart", "(C)Z", ('$' as JavaChar,)) + .await? + ); + assert!( + jvm.invoke_static::<_, bool>("java/lang/Character", "isJavaIdentifierPart", "(C)Z", ('7' as JavaChar,)) + .await? + ); + assert!( + jvm.invoke_static::<_, bool>("java/lang/Character", "isWhitespace", "(C)Z", ('\n' as JavaChar,)) + .await? + ); + for value in ['\u{0009}', '\u{000a}', '\u{000c}', '\u{000d}', '\u{0020}'] { + assert!( + jvm.invoke_static::<_, bool>("java/lang/Character", "isSpace", "(C)Z", (value as JavaChar,)) + .await? + ); + } + for value in ['\u{000b}', '\u{001c}'] { + assert!( + !jvm.invoke_static::<_, bool>("java/lang/Character", "isSpace", "(C)Z", (value as JavaChar,)) + .await? + ); + } + assert!( + jvm.invoke_static::<_, bool>("java/lang/Character", "isSpaceChar", "(C)Z", ('\u{00a0}' as JavaChar,)) + .await? + ); + assert!( + !jvm.invoke_static::<_, bool>("java/lang/Character", "isWhitespace", "(C)Z", ('\u{00a0}' as JavaChar,)) + .await? + ); + + assert!( + jvm.invoke_static::<_, bool>("java/lang/Character", "isLetter", "(C)Z", ('รฉ' as JavaChar,)) + .await? + ); + assert!( + jvm.invoke_static::<_, bool>("java/lang/Character", "isUpperCase", "(C)Z", ('ฮฉ' as JavaChar,)) + .await? + ); + assert_eq!( + jvm.invoke_static::<_, JavaChar>("java/lang/Character", "toLowerCase", "(C)C", ('ฮฉ' as JavaChar,)) + .await?, + 'ฯ‰' as JavaChar + ); + assert_eq!( + jvm.invoke_static::<_, JavaChar>("java/lang/Character", "toUpperCase", "(C)C", ('รŸ' as JavaChar,)) + .await?, + 'รŸ' as JavaChar + ); + + let arabic_three = 'ูฃ' as JavaChar; + assert!( + jvm.invoke_static::<_, bool>("java/lang/Character", "isDigit", "(C)Z", (arabic_three,)) + .await? + ); + assert_eq!( + jvm.invoke_static::<_, i32>("java/lang/Character", "digit", "(CI)I", (arabic_three, 10)) + .await?, + 3 + ); + assert_eq!( + jvm.invoke_static::<_, i32>("java/lang/Character", "getNumericValue", "(C)I", (arabic_three,)) + .await?, + 3 + ); + assert_eq!( + jvm.invoke_static::<_, i32>("java/lang/Character", "digit", "(CI)I", ('๏ผก' as JavaChar, 16)) + .await?, + 10 + ); + assert_eq!( + jvm.invoke_static::<_, i32>("java/lang/Character", "digit", "(CI)I", ('g' as JavaChar, 16)) + .await?, + -1 + ); + assert_eq!( + jvm.invoke_static::<_, JavaChar>("java/lang/Character", "forDigit", "(II)C", (15, 16)) + .await?, + 'f' as JavaChar + ); + assert_eq!( + jvm.invoke_static::<_, JavaChar>("java/lang/Character", "forDigit", "(II)C", (16, 16)) + .await?, + 0 + ); + + assert_eq!( + jvm.invoke_static::<_, i32>("java/lang/Character", "getType", "(C)I", ('A' as JavaChar,)) + .await?, + 1 + ); + assert_eq!( + jvm.invoke_static::<_, i32>("java/lang/Character", "getType", "(C)I", ('รฉ' as JavaChar,)) + .await?, + 2 + ); + assert_eq!( + jvm.invoke_static::<_, i32>("java/lang/Character", "getType", "(C)I", (arabic_three,)) + .await?, + 9 + ); + assert_eq!( + jvm.invoke_static::<_, i32>("java/lang/Character", "getType", "(C)I", ('\u{0301}' as JavaChar,)) + .await?, + 6 + ); + assert_eq!( + jvm.invoke_static::<_, i32>("java/lang/Character", "getType", "(C)I", ('โ…ง' as JavaChar,)) + .await?, + 10 + ); + assert!( + !jvm.invoke_static::<_, bool>("java/lang/Character", "isDefined", "(C)Z", (0x0378 as JavaChar,)) + .await? + ); + + for (value, expected_type) in [('ยก', 24), ('ยซ', 21), ('ยป', 22), ('ยง', 24)] { + assert_eq!( + jvm.invoke_static::<_, i32>("java/lang/Character", "getType", "(C)I", (value as JavaChar,)) + .await?, + expected_type + ); + assert!( + jvm.invoke_static::<_, bool>("java/lang/Character", "isDefined", "(C)Z", (value as JavaChar,)) + .await? + ); + } + assert_eq!( + jvm.invoke_static::<_, i32>("java/lang/Character", "getType", "(C)I", (0xe000 as JavaChar,)) + .await?, + 18 + ); + assert_eq!( + jvm.invoke_static::<_, i32>("java/lang/Character", "getType", "(C)I", (0xfeff as JavaChar,)) + .await?, + 16 + ); + assert_eq!( + jvm.invoke_static::<_, i32>("java/lang/Character", "getType", "(C)I", (0xffff as JavaChar,)) + .await?, + 0 + ); + + for value in ['ยฃ', 'โ€ฟ', 'โ… '] { + assert!( + jvm.invoke_static::<_, bool>("java/lang/Character", "isJavaIdentifierStart", "(C)Z", (value as JavaChar,)) + .await? + ); + } + for value in ['โ… ', '\u{0301}', '\u{0000}'] { + assert!( + jvm.invoke_static::<_, bool>("java/lang/Character", "isJavaIdentifierPart", "(C)Z", (value as JavaChar,)) + .await? + ); + } + for value in ['ยฃ', 'โ€ฟ', 'โ… '] { + assert!( + jvm.invoke_static::<_, bool>("java/lang/Character", "isJavaLetter", "(C)Z", (value as JavaChar,)) + .await? + ); + } + for value in ['7', 'โ… ', '\u{0301}', '\u{0000}'] { + assert!( + jvm.invoke_static::<_, bool>("java/lang/Character", "isJavaLetterOrDigit", "(C)Z", (value as JavaChar,)) + .await? + ); + } + + Ok(()) +} + +#[tokio::test] +async fn test_character_surrogate_and_compare_errors() -> Result<()> { + let jvm = test_jvm().await?; + let surrogate = 0xd800 as JavaChar; + + let value: ClassInstanceRef = jvm.new_class("java/lang/Character", "(C)V", (surrogate,)).await?.into(); + assert_eq!(jvm.invoke_virtual::<_, JavaChar>(&value, "charValue", "()C", ()).await?, surrogate); + let text: ClassInstanceRef = jvm.invoke_virtual(&value, "toString", "()Ljava/lang/String;", ()).await?; + let chars: ClassInstanceRef> = jvm.get_field(&text, "value", "[C").await?; + assert_eq!(jvm.load_array::(&chars, 0, 1).await?, [surrogate]); + + for method in [ + "isLowerCase", + "isUpperCase", + "isTitleCase", + "isDigit", + "isDefined", + "isLetter", + "isLetterOrDigit", + "isJavaIdentifierStart", + "isJavaIdentifierPart", + "isUnicodeIdentifierStart", + "isUnicodeIdentifierPart", + "isIdentifierIgnorable", + "isSpace", + "isSpaceChar", + "isWhitespace", + "isISOControl", + ] { + assert!(!jvm.invoke_static::<_, bool>("java/lang/Character", method, "(C)Z", (surrogate,)).await?); + } + for method in ["toLowerCase", "toUpperCase", "toTitleCase"] { + assert_eq!( + jvm.invoke_static::<_, JavaChar>("java/lang/Character", method, "(C)C", (surrogate,)) + .await?, + surrogate + ); + } + assert_eq!( + jvm.invoke_static::<_, i32>("java/lang/Character", "digit", "(CI)I", (surrogate, 10)) + .await?, + -1 + ); + assert_eq!( + jvm.invoke_static::<_, i32>("java/lang/Character", "getNumericValue", "(C)I", (surrogate,)) + .await?, + -1 + ); + assert_eq!( + jvm.invoke_static::<_, i32>("java/lang/Character", "getType", "(C)I", (surrogate,)) + .await?, + 0 + ); + + let other: ClassInstanceRef = jvm.new_class("java/lang/Character", "(C)V", ('Z' as JavaChar,)).await?.into(); + assert_eq!( + jvm.invoke_virtual::<_, i32>(&value, "compareTo", "(Ljava/lang/Character;)I", (other.clone(),)) + .await?, + 1 + ); + assert_eq!( + jvm.invoke_virtual::<_, i32>(&other, "compareTo", "(Ljava/lang/Object;)I", (value.clone(),)) + .await?, + -1 + ); + + let null_character: ClassInstanceRef = None.into(); + let result: Result = jvm + .invoke_virtual(&value, "compareTo", "(Ljava/lang/Character;)I", (null_character,)) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("Character.compareTo(Character) must reject null"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + + let null_object: ClassInstanceRef = None.into(); + let result: Result = jvm.invoke_virtual(&value, "compareTo", "(Ljava/lang/Object;)I", (null_object,)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("Character.compareTo(Object) must reject null"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + + let wrong = jvm.new_class("java/lang/Object", "()V", ()).await?; + let result: Result = jvm + .invoke_virtual(&value, "compareTo", "(Ljava/lang/Character;)I", (wrong.clone(),)) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("typed Character.compareTo must reject a non-Character instance"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/ClassCastException")); + + let result: Result = jvm.invoke_virtual(&value, "compareTo", "(Ljava/lang/Object;)I", (wrong,)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("raw Character.compareTo must reject a non-Character instance"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/ClassCastException")); + + Ok(()) +} diff --git a/java_runtime/tests/classes/java/lang/test_class.rs b/java_runtime/tests/classes/java/lang/test_class.rs index 9e5cab51..6ad89df1 100644 --- a/java_runtime/tests/classes/java/lang/test_class.rs +++ b/java_runtime/tests/classes/java/lang/test_class.rs @@ -1,6 +1,6 @@ -use java_runtime::classes::java::lang::Class; +use java_runtime::classes::java::lang::{Class, String}; use jvm::{ - ClassInstanceRef, Result, + ClassInstanceRef, JavaError, Result, runtime::{JavaLangClass, JavaLangString}, }; @@ -56,5 +56,113 @@ async fn test_for_name() -> Result<()> { let rust_class = JavaLangClass::to_rust_class(&jvm, &class).await?; assert_eq!(rust_class.name(), "java/lang/String"); + let result: Result> = jvm + .invoke_static("java/lang/Class", "forName", "(Ljava/lang/String;)Ljava/lang/Class;", (None,)) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("Class.forName(null) must throw NullPointerException"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + + Ok(()) +} + +#[tokio::test] +async fn test_primitive_class_api() -> Result<()> { + let jvm = test_jvm().await?; + + for name in ["boolean", "byte", "char", "short", "int", "long", "float", "double"] { + let primitive = JavaLangClass::from_rust_primitive(&jvm, name).await?; + let primitive_name = JavaLangClass::name(&jvm, &primitive).await?; + assert_eq!(primitive_name, name); + + let virtual_name: ClassInstanceRef = jvm.invoke_virtual(&primitive, "getName", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &virtual_name).await?, name); + + let is_primitive: bool = jvm.invoke_virtual(&primitive, "isPrimitive", "()Z", ()).await?; + assert!(is_primitive); + + let class_name = JavaLangString::from_rust_string(&jvm, name).await?; + let result: Result> = jvm + .invoke_static("java/lang/Class", "forName", "(Ljava/lang/String;)Ljava/lang/Class;", (class_name,)) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("Class.forName must reject primitive source names"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/ClassNotFoundException")); + } + + let object_class = jvm.resolve_class("java/lang/Object").await?.java_class(); + let string_class = jvm.resolve_class("java/lang/String").await?.java_class(); + let primitive = JavaLangClass::from_rust_primitive(&jvm, "int").await?; + let other_primitive = JavaLangClass::from_rust_primitive(&jvm, "long").await?; + + let result: bool = jvm + .invoke_virtual(&primitive, "isAssignableFrom", "(Ljava/lang/Class;)Z", (primitive.clone(),)) + .await?; + assert!(result); + + let result: bool = jvm + .invoke_virtual(&primitive, "isAssignableFrom", "(Ljava/lang/Class;)Z", (other_primitive,)) + .await?; + assert!(!result); + + let result: bool = jvm + .invoke_virtual(&primitive, "isAssignableFrom", "(Ljava/lang/Class;)Z", (string_class.clone(),)) + .await?; + assert!(!result); + + let result: bool = jvm + .invoke_virtual(&object_class, "isAssignableFrom", "(Ljava/lang/Class;)Z", (primitive,)) + .await?; + assert!(!result); + + let result: Result = jvm + .invoke_virtual(&object_class, "isAssignableFrom", "(Ljava/lang/Class;)Z", (None,)) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("Class.isAssignableFrom(null) must throw NullPointerException"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + + Ok(()) +} + +#[tokio::test] +async fn test_wrapper_type_fields_survive_gc() -> Result<()> { + let jvm = test_jvm().await?; + + for (wrapper, primitive_name) in [ + ("java/lang/Boolean", "boolean"), + ("java/lang/Byte", "byte"), + ("java/lang/Character", "char"), + ("java/lang/Short", "short"), + ("java/lang/Integer", "int"), + ("java/lang/Long", "long"), + ("java/lang/Float", "float"), + ("java/lang/Double", "double"), + ] { + let typ = jvm.get_static_field(wrapper, "TYPE", "Ljava/lang/Class;").await?; + let name: ClassInstanceRef = jvm.invoke_virtual(&typ, "getName", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &name).await?, primitive_name); + assert!(jvm.invoke_virtual::<_, bool>(&typ, "isPrimitive", "()Z", ()).await?); + } + + let _: () = jvm.invoke_static("java/lang/System", "gc", "()V", ()).await?; + + for wrapper in [ + "java/lang/Boolean", + "java/lang/Byte", + "java/lang/Character", + "java/lang/Short", + "java/lang/Integer", + "java/lang/Long", + "java/lang/Float", + "java/lang/Double", + ] { + let typ = jvm.get_static_field(wrapper, "TYPE", "Ljava/lang/Class;").await?; + assert!(jvm.invoke_virtual::<_, bool>(&typ, "isPrimitive", "()Z", ()).await?); + } + Ok(()) } diff --git a/java_runtime/tests/classes/java/lang/test_double.rs b/java_runtime/tests/classes/java/lang/test_double.rs new file mode 100644 index 00000000..cfe270c9 --- /dev/null +++ b/java_runtime/tests/classes/java/lang/test_double.rs @@ -0,0 +1,243 @@ +use java_runtime::classes::java::lang::{Double, String}; +use jvm::{ClassInstanceRef, JavaError, Result, runtime::JavaLangString}; + +use test_utils::test_jvm; + +#[tokio::test] +async fn test_double_constructors_and_number_conversions() -> Result<()> { + let jvm = test_jvm().await?; + + let value = jvm.new_class("java/lang/Double", "(D)V", (65_537.75f64,)).await?; + assert_eq!(jvm.invoke_virtual::<_, f64>(&value, "doubleValue", "()D", ()).await?, 65_537.75); + assert_eq!(jvm.invoke_virtual::<_, f32>(&value, "floatValue", "()F", ()).await?, 65_537.75f32); + assert_eq!(jvm.invoke_virtual::<_, i32>(&value, "intValue", "()I", ()).await?, 65_537); + assert_eq!(jvm.invoke_virtual::<_, i64>(&value, "longValue", "()J", ()).await?, 65_537); + assert_eq!(jvm.invoke_virtual::<_, i8>(&value, "byteValue", "()B", ()).await?, 1); + assert_eq!(jvm.invoke_virtual::<_, i16>(&value, "shortValue", "()S", ()).await?, 1); + + let text = JavaLangString::from_rust_string(&jvm, "-3.5").await?; + let from_string = jvm.new_class("java/lang/Double", "(Ljava/lang/String;)V", (text,)).await?; + assert_eq!(jvm.invoke_virtual::<_, f64>(&from_string, "doubleValue", "()D", ()).await?, -3.5); + + Ok(()) +} + +#[tokio::test] +async fn test_double_parse_value_of_and_format() -> Result<()> { + let jvm = test_jvm().await?; + + for (text, expected) in [(" \t-1.25e2D\n", -125.0), (".5f", 0.5), ("+42.", 42.0)] { + let text = JavaLangString::from_rust_string(&jvm, text).await?; + let parsed: f64 = jvm + .invoke_static("java/lang/Double", "parseDouble", "(Ljava/lang/String;)D", (text,)) + .await?; + assert_eq!(parsed, expected); + } + for suffix in ['f', 'F', 'd', 'D'] { + let text = JavaLangString::from_rust_string(&jvm, &format!("1.5{suffix}")).await?; + let parsed: f64 = jvm + .invoke_static("java/lang/Double", "parseDouble", "(Ljava/lang/String;)D", (text,)) + .await?; + assert_eq!(parsed, 1.5); + } + + let infinity = JavaLangString::from_rust_string(&jvm, "-Infinity").await?; + let infinity: f64 = jvm + .invoke_static("java/lang/Double", "parseDouble", "(Ljava/lang/String;)D", (infinity,)) + .await?; + assert_eq!(infinity, f64::NEG_INFINITY); + + let nan = JavaLangString::from_rust_string(&jvm, "+NaN").await?; + let nan: ClassInstanceRef = jvm + .invoke_static("java/lang/Double", "valueOf", "(Ljava/lang/String;)Ljava/lang/Double;", (nan,)) + .await?; + assert!(jvm.invoke_virtual::<_, bool>(&nan, "isNaN", "()Z", ()).await?); + + for (value, expected) in [(12.0f64, "12.0"), (-0.0, "-0.0"), (10_000_000.0, "1.0E7"), (0.000_125, "1.25E-4")] { + let text: ClassInstanceRef = jvm + .invoke_static("java/lang/Double", "toString", "(D)Ljava/lang/String;", (value,)) + .await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &text).await?, expected); + } + + for malformed in ["", "nan", "Infinityd", "1e", ".", "1_0", "0x1.0p0", "1.0 dd"] { + let malformed = JavaLangString::from_rust_string(&jvm, malformed).await?; + let result: Result = jvm + .invoke_static("java/lang/Double", "parseDouble", "(Ljava/lang/String;)D", (malformed,)) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("Double.parseDouble must reject malformed input"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NumberFormatException")); + } + + let null_result: Result = jvm + .invoke_static("java/lang/Double", "parseDouble", "(Ljava/lang/String;)D", (None,)) + .await; + let Err(JavaError::JavaException(exception)) = null_result else { + panic!("Double.parseDouble(null) must throw NullPointerException"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + + for (value, expected) in [(f64::from_bits(1), "4.9E-324"), (f64::from_bits(0x8000_0000_0000_0001), "-4.9E-324")] { + let text: ClassInstanceRef = jvm + .invoke_static("java/lang/Double", "toString", "(D)Ljava/lang/String;", (value,)) + .await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &text).await?, expected); + } + + let min = JavaLangString::from_rust_string(&jvm, "4.9e-324").await?; + assert_eq!( + jvm.invoke_static::<_, f64>("java/lang/Double", "parseDouble", "(Ljava/lang/String;)D", (min,)) + .await? + .to_bits(), + 1 + ); + let overflow = JavaLangString::from_rust_string(&jvm, "1e10000").await?; + assert_eq!( + jvm.invoke_static::<_, f64>("java/lang/Double", "parseDouble", "(Ljava/lang/String;)D", (overflow,)) + .await?, + f64::INFINITY + ); + let underflow = JavaLangString::from_rust_string(&jvm, "-1e-10000").await?; + assert_eq!( + jvm.invoke_static::<_, f64>("java/lang/Double", "parseDouble", "(Ljava/lang/String;)D", (underflow,)) + .await? + .to_bits(), + (-0.0f64).to_bits() + ); + + Ok(()) +} + +#[tokio::test] +async fn test_double_bits_equality_hash_and_comparison() -> Result<()> { + let jvm = test_jvm().await?; + + let payload_nan_a: f64 = jvm + .invoke_static("java/lang/Double", "longBitsToDouble", "(J)D", (0x7ff8_0000_0000_0001i64,)) + .await?; + let payload_nan_b: f64 = jvm + .invoke_static("java/lang/Double", "longBitsToDouble", "(J)D", (0x7fff_ffff_ffff_ffffi64,)) + .await?; + assert_eq!( + jvm.invoke_static::<_, i64>("java/lang/Double", "doubleToLongBits", "(D)J", (payload_nan_a,)) + .await?, + 0x7ff8_0000_0000_0000 + ); + + let raw = (-12.5f64).to_bits() as i64; + let round_trip: f64 = jvm.invoke_static("java/lang/Double", "longBitsToDouble", "(J)D", (raw,)).await?; + assert_eq!( + jvm.invoke_static::<_, i64>("java/lang/Double", "doubleToLongBits", "(D)J", (round_trip,)) + .await?, + raw + ); + + let nan_a = jvm.new_class("java/lang/Double", "(D)V", (payload_nan_a,)).await?; + let nan_b = jvm.new_class("java/lang/Double", "(D)V", (payload_nan_b,)).await?; + assert!( + jvm.invoke_virtual::<_, bool>(&nan_a, "equals", "(Ljava/lang/Object;)Z", (nan_b.clone(),)) + .await? + ); + assert_eq!(jvm.invoke_virtual::<_, i32>(&nan_a, "hashCode", "()I", ()).await?, 0x7ff8_0000); + assert_eq!( + jvm.invoke_virtual::<_, i32>(&nan_a, "compareTo", "(Ljava/lang/Double;)I", (nan_b,)) + .await?, + 0 + ); + + let negative_zero = jvm.new_class("java/lang/Double", "(D)V", (-0.0f64,)).await?; + let positive_zero = jvm.new_class("java/lang/Double", "(D)V", (0.0f64,)).await?; + assert!( + !jvm.invoke_virtual::<_, bool>(&negative_zero, "equals", "(Ljava/lang/Object;)Z", (positive_zero.clone(),)) + .await? + ); + assert_eq!( + jvm.invoke_virtual::<_, i32>(&negative_zero, "compareTo", "(Ljava/lang/Double;)I", (positive_zero.clone(),),) + .await?, + -1 + ); + assert_eq!( + jvm.invoke_virtual::<_, i32>(&negative_zero, "compareTo", "(Ljava/lang/Object;)I", (positive_zero,)) + .await?, + -1 + ); + assert_eq!(jvm.invoke_virtual::<_, i32>(&negative_zero, "hashCode", "()I", ()).await?, i32::MIN); + assert_eq!( + jvm.invoke_virtual::<_, i32>(&jvm.new_class("java/lang/Double", "(D)V", (0.0f64,)).await?, "hashCode", "()I", (),) + .await?, + 0 + ); + + let infinity = jvm.new_class("java/lang/Double", "(D)V", (f64::INFINITY,)).await?; + assert_eq!( + jvm.invoke_virtual::<_, i32>(&nan_a, "compareTo", "(Ljava/lang/Double;)I", (infinity,)) + .await?, + 1 + ); + + let typed_null: Result = jvm.invoke_virtual(&negative_zero, "compareTo", "(Ljava/lang/Double;)I", (None,)).await; + let Err(JavaError::JavaException(exception)) = typed_null else { + panic!("Double typed compare null must throw NPE"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + + let raw_null: Result = jvm.invoke_virtual(&negative_zero, "compareTo", "(Ljava/lang/Object;)I", (None,)).await; + let Err(JavaError::JavaException(exception)) = raw_null else { + panic!("Double raw compare null must throw NPE"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + + let object = jvm.new_class("java/lang/Object", "()V", ()).await?; + let wrong_type: Result = jvm.invoke_virtual(&negative_zero, "compareTo", "(Ljava/lang/Object;)I", (object,)).await; + let Err(JavaError::JavaException(exception)) = wrong_type else { + panic!("Double raw compare wrong type must throw CCE"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/ClassCastException")); + + for (value, int_value, long_value) in [ + (f64::NAN, 0, 0i64), + (f64::INFINITY, i32::MAX, i64::MAX), + (f64::NEG_INFINITY, i32::MIN, i64::MIN), + ] { + let wrapper = jvm.new_class("java/lang/Double", "(D)V", (value,)).await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&wrapper, "intValue", "()I", ()).await?, int_value); + assert_eq!(jvm.invoke_virtual::<_, i64>(&wrapper, "longValue", "()J", ()).await?, long_value); + } + let maximum = jvm.new_class("java/lang/Double", "(D)V", (f64::MAX,)).await?; + assert!(jvm.invoke_virtual::<_, f32>(&maximum, "floatValue", "()F", ()).await?.is_infinite()); + let minimum = jvm.new_class("java/lang/Double", "(D)V", (f64::from_bits(1),)).await?; + assert_eq!(jvm.invoke_virtual::<_, f32>(&minimum, "floatValue", "()F", ()).await?.to_bits(), 0); + + Ok(()) +} + +#[tokio::test] +async fn test_double_constants_type_and_predicates() -> Result<()> { + let jvm = test_jvm().await?; + + assert_eq!( + jvm.get_static_field::("java/lang/Double", "POSITIVE_INFINITY", "D").await?, + f64::INFINITY + ); + assert_eq!( + jvm.get_static_field::("java/lang/Double", "NEGATIVE_INFINITY", "D").await?, + f64::NEG_INFINITY + ); + assert!(jvm.get_static_field::("java/lang/Double", "NaN", "D").await?.is_nan()); + assert_eq!(jvm.get_static_field::("java/lang/Double", "MAX_VALUE", "D").await?, f64::MAX); + assert_eq!(jvm.get_static_field::("java/lang/Double", "MIN_VALUE", "D").await?.to_bits(), 1); + assert!(jvm.invoke_static::<_, bool>("java/lang/Double", "isNaN", "(D)Z", (f64::NAN,)).await?); + assert!( + jvm.invoke_static::<_, bool>("java/lang/Double", "isInfinite", "(D)Z", (f64::NEG_INFINITY,)) + .await? + ); + + let typ = jvm.get_static_field("java/lang/Double", "TYPE", "Ljava/lang/Class;").await?; + let name: ClassInstanceRef = jvm.invoke_virtual(&typ, "getName", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &name).await?, "double"); + assert!(jvm.invoke_virtual::<_, bool>(&typ, "isPrimitive", "()Z", ()).await?); + + Ok(()) +} diff --git a/java_runtime/tests/classes/java/lang/test_float.rs b/java_runtime/tests/classes/java/lang/test_float.rs new file mode 100644 index 00000000..1d1ef3f7 --- /dev/null +++ b/java_runtime/tests/classes/java/lang/test_float.rs @@ -0,0 +1,233 @@ +use java_runtime::classes::java::lang::{Float, String}; +use jvm::{ClassInstanceRef, JavaError, Result, runtime::JavaLangString}; + +use test_utils::test_jvm; + +#[tokio::test] +async fn test_float_constructors_and_number_conversions() -> Result<()> { + let jvm = test_jvm().await?; + + let value = jvm.new_class("java/lang/Float", "(D)V", (130.75f64,)).await?; + assert_eq!(jvm.invoke_virtual::<_, f32>(&value, "floatValue", "()F", ()).await?, 130.75); + assert_eq!(jvm.invoke_virtual::<_, f64>(&value, "doubleValue", "()D", ()).await?, 130.75); + assert_eq!(jvm.invoke_virtual::<_, i32>(&value, "intValue", "()I", ()).await?, 130); + assert_eq!(jvm.invoke_virtual::<_, i64>(&value, "longValue", "()J", ()).await?, 130); + assert_eq!(jvm.invoke_virtual::<_, i8>(&value, "byteValue", "()B", ()).await?, -126); + assert_eq!(jvm.invoke_virtual::<_, i16>(&value, "shortValue", "()S", ()).await?, 130); + + let text = JavaLangString::from_rust_string(&jvm, "-3.5").await?; + let from_string = jvm.new_class("java/lang/Float", "(Ljava/lang/String;)V", (text,)).await?; + assert_eq!(jvm.invoke_virtual::<_, f32>(&from_string, "floatValue", "()F", ()).await?, -3.5); + + Ok(()) +} + +#[tokio::test] +async fn test_float_parse_value_of_and_format() -> Result<()> { + let jvm = test_jvm().await?; + + for (text, expected) in [(" \t-1.25e2F\n", -125.0), (".5d", 0.5), ("+42.", 42.0)] { + let text = JavaLangString::from_rust_string(&jvm, text).await?; + let parsed: f32 = jvm + .invoke_static("java/lang/Float", "parseFloat", "(Ljava/lang/String;)F", (text,)) + .await?; + assert_eq!(parsed, expected); + } + for suffix in ['f', 'F', 'd', 'D'] { + let text = JavaLangString::from_rust_string(&jvm, &format!("1.5{suffix}")).await?; + let parsed: f32 = jvm + .invoke_static("java/lang/Float", "parseFloat", "(Ljava/lang/String;)F", (text,)) + .await?; + assert_eq!(parsed, 1.5); + } + + let infinity = JavaLangString::from_rust_string(&jvm, "+Infinity").await?; + let infinity: f32 = jvm + .invoke_static("java/lang/Float", "parseFloat", "(Ljava/lang/String;)F", (infinity,)) + .await?; + assert_eq!(infinity, f32::INFINITY); + + let nan = JavaLangString::from_rust_string(&jvm, "-NaN").await?; + let nan: ClassInstanceRef = jvm + .invoke_static("java/lang/Float", "valueOf", "(Ljava/lang/String;)Ljava/lang/Float;", (nan,)) + .await?; + assert!(jvm.invoke_virtual::<_, bool>(&nan, "isNaN", "()Z", ()).await?); + + for (value, expected) in [(12.0f32, "12.0"), (-0.0, "-0.0"), (10_000_000.0, "1.0E7"), (0.000_125, "1.25E-4")] { + let text: ClassInstanceRef = jvm + .invoke_static("java/lang/Float", "toString", "(F)Ljava/lang/String;", (value,)) + .await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &text).await?, expected); + } + + for malformed in ["", "nan", "Infinityf", "1e", ".", "1_0", "0x1.0p0", "1.0 ff"] { + let malformed = JavaLangString::from_rust_string(&jvm, malformed).await?; + let result: Result = jvm + .invoke_static("java/lang/Float", "parseFloat", "(Ljava/lang/String;)F", (malformed,)) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("Float.parseFloat must reject malformed input"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NumberFormatException")); + } + + let null_result: Result = jvm.invoke_static("java/lang/Float", "parseFloat", "(Ljava/lang/String;)F", (None,)).await; + let Err(JavaError::JavaException(exception)) = null_result else { + panic!("Float.parseFloat(null) must throw NullPointerException"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + + for (value, expected) in [(f32::from_bits(1), "1.4E-45"), (f32::from_bits(0x8000_0001), "-1.4E-45")] { + let text: ClassInstanceRef = jvm + .invoke_static("java/lang/Float", "toString", "(F)Ljava/lang/String;", (value,)) + .await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &text).await?, expected); + } + + let min = JavaLangString::from_rust_string(&jvm, "1.4e-45").await?; + assert_eq!( + jvm.invoke_static::<_, f32>("java/lang/Float", "parseFloat", "(Ljava/lang/String;)F", (min,)) + .await? + .to_bits(), + 1 + ); + let overflow = JavaLangString::from_rust_string(&jvm, "1e1000").await?; + assert_eq!( + jvm.invoke_static::<_, f32>("java/lang/Float", "parseFloat", "(Ljava/lang/String;)F", (overflow,)) + .await?, + f32::INFINITY + ); + let underflow = JavaLangString::from_rust_string(&jvm, "-1e-1000").await?; + assert_eq!( + jvm.invoke_static::<_, f32>("java/lang/Float", "parseFloat", "(Ljava/lang/String;)F", (underflow,)) + .await? + .to_bits(), + (-0.0f32).to_bits() + ); + + Ok(()) +} + +#[tokio::test] +async fn test_float_bits_equality_hash_and_comparison() -> Result<()> { + let jvm = test_jvm().await?; + + let payload_nan_a: f32 = jvm.invoke_static("java/lang/Float", "intBitsToFloat", "(I)F", (0x7fc0_0001i32,)).await?; + let payload_nan_b: f32 = jvm.invoke_static("java/lang/Float", "intBitsToFloat", "(I)F", (0x7fff_ffffi32,)).await?; + assert_eq!( + jvm.invoke_static::<_, i32>("java/lang/Float", "floatToIntBits", "(F)I", (payload_nan_a,)) + .await?, + 0x7fc0_0000 + ); + + let raw = (-12.5f32).to_bits() as i32; + let round_trip: f32 = jvm.invoke_static("java/lang/Float", "intBitsToFloat", "(I)F", (raw,)).await?; + assert_eq!( + jvm.invoke_static::<_, i32>("java/lang/Float", "floatToIntBits", "(F)I", (round_trip,)) + .await?, + raw + ); + + let nan_a = jvm.new_class("java/lang/Float", "(F)V", (payload_nan_a,)).await?; + let nan_b = jvm.new_class("java/lang/Float", "(F)V", (payload_nan_b,)).await?; + assert!( + jvm.invoke_virtual::<_, bool>(&nan_a, "equals", "(Ljava/lang/Object;)Z", (nan_b.clone(),)) + .await? + ); + assert_eq!(jvm.invoke_virtual::<_, i32>(&nan_a, "hashCode", "()I", ()).await?, 0x7fc0_0000); + assert_eq!( + jvm.invoke_virtual::<_, i32>(&nan_a, "compareTo", "(Ljava/lang/Float;)I", (nan_b,)) + .await?, + 0 + ); + + let negative_zero = jvm.new_class("java/lang/Float", "(F)V", (-0.0f32,)).await?; + let positive_zero = jvm.new_class("java/lang/Float", "(F)V", (0.0f32,)).await?; + assert!( + !jvm.invoke_virtual::<_, bool>(&negative_zero, "equals", "(Ljava/lang/Object;)Z", (positive_zero.clone(),)) + .await? + ); + assert_eq!( + jvm.invoke_virtual::<_, i32>(&negative_zero, "compareTo", "(Ljava/lang/Float;)I", (positive_zero.clone(),),) + .await?, + -1 + ); + assert_eq!( + jvm.invoke_virtual::<_, i32>(&negative_zero, "compareTo", "(Ljava/lang/Object;)I", (positive_zero,)) + .await?, + -1 + ); + assert_eq!(jvm.invoke_virtual::<_, i32>(&negative_zero, "hashCode", "()I", ()).await?, i32::MIN); + assert_eq!( + jvm.invoke_virtual::<_, i32>(&jvm.new_class("java/lang/Float", "(F)V", (0.0f32,)).await?, "hashCode", "()I", (),) + .await?, + 0 + ); + + let infinity = jvm.new_class("java/lang/Float", "(F)V", (f32::INFINITY,)).await?; + assert_eq!( + jvm.invoke_virtual::<_, i32>(&nan_a, "compareTo", "(Ljava/lang/Float;)I", (infinity,)) + .await?, + 1 + ); + + let typed_null: Result = jvm.invoke_virtual(&negative_zero, "compareTo", "(Ljava/lang/Float;)I", (None,)).await; + let Err(JavaError::JavaException(exception)) = typed_null else { + panic!("Float typed compare null must throw NPE"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + + let raw_null: Result = jvm.invoke_virtual(&negative_zero, "compareTo", "(Ljava/lang/Object;)I", (None,)).await; + let Err(JavaError::JavaException(exception)) = raw_null else { + panic!("Float raw compare null must throw NPE"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + + let object = jvm.new_class("java/lang/Object", "()V", ()).await?; + let wrong_type: Result = jvm.invoke_virtual(&negative_zero, "compareTo", "(Ljava/lang/Object;)I", (object,)).await; + let Err(JavaError::JavaException(exception)) = wrong_type else { + panic!("Float raw compare wrong type must throw CCE"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/ClassCastException")); + + for (value, int_value, long_value) in [ + (f32::NAN, 0, 0i64), + (f32::INFINITY, i32::MAX, i64::MAX), + (f32::NEG_INFINITY, i32::MIN, i64::MIN), + ] { + let wrapper = jvm.new_class("java/lang/Float", "(F)V", (value,)).await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&wrapper, "intValue", "()I", ()).await?, int_value); + assert_eq!(jvm.invoke_virtual::<_, i64>(&wrapper, "longValue", "()J", ()).await?, long_value); + } + + Ok(()) +} + +#[tokio::test] +async fn test_float_constants_type_and_predicates() -> Result<()> { + let jvm = test_jvm().await?; + + assert_eq!( + jvm.get_static_field::("java/lang/Float", "POSITIVE_INFINITY", "F").await?, + f32::INFINITY + ); + assert_eq!( + jvm.get_static_field::("java/lang/Float", "NEGATIVE_INFINITY", "F").await?, + f32::NEG_INFINITY + ); + assert!(jvm.get_static_field::("java/lang/Float", "NaN", "F").await?.is_nan()); + assert_eq!(jvm.get_static_field::("java/lang/Float", "MAX_VALUE", "F").await?, f32::MAX); + assert_eq!(jvm.get_static_field::("java/lang/Float", "MIN_VALUE", "F").await?.to_bits(), 1); + assert!(jvm.invoke_static::<_, bool>("java/lang/Float", "isNaN", "(F)Z", (f32::NAN,)).await?); + assert!( + jvm.invoke_static::<_, bool>("java/lang/Float", "isInfinite", "(F)Z", (f32::NEG_INFINITY,)) + .await? + ); + + let typ = jvm.get_static_field("java/lang/Float", "TYPE", "Ljava/lang/Class;").await?; + let name: ClassInstanceRef = jvm.invoke_virtual(&typ, "getName", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &name).await?, "float"); + assert!(jvm.invoke_virtual::<_, bool>(&typ, "isPrimitive", "()Z", ()).await?); + + Ok(()) +} diff --git a/java_runtime/tests/classes/java/lang/test_integer.rs b/java_runtime/tests/classes/java/lang/test_integer.rs index fc1cf600..fab0648d 100644 --- a/java_runtime/tests/classes/java/lang/test_integer.rs +++ b/java_runtime/tests/classes/java/lang/test_integer.rs @@ -1,4 +1,5 @@ -use jvm::{JavaError, Result, runtime::JavaLangString}; +use java_runtime::classes::java::lang::{Integer, Object, String}; +use jvm::{ClassInstanceRef, JavaError, Result, runtime::JavaLangString}; use test_utils::test_jvm; @@ -13,6 +14,9 @@ async fn test_parse_int() -> Result<()> { .await? ); + let boxed: ClassInstanceRef = jvm.invoke_static("java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;", (42,)).await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&boxed, "intValue", "()I", ()).await?, 42); + Ok(()) } @@ -32,3 +36,199 @@ async fn test_parse_int_invalid() -> Result<()> { Ok(()) } + +#[tokio::test] +async fn test_integer_strict_api() -> Result<()> { + let jvm = test_jvm().await?; + + for (text, radix, expected) in [ + ("7fffffff", 16, i32::MAX), + ("-80000000", 16, i32::MIN), + ("z", 36, 35), + ("\u{ff21}", 16, 10), + ("\u{0661}\u{0662}\u{0663}", 10, 123), + ] { + let string = JavaLangString::from_rust_string(&jvm, text).await?; + let value: i32 = jvm + .invoke_static("java/lang/Integer", "parseInt", "(Ljava/lang/String;I)I", (string, radix)) + .await?; + assert_eq!(value, expected); + } + for (text, radix, expected) in [ + ("-10000000000000000000000000000000", 2, i32::MIN), + ("1111111111111111111111111111111", 2, i32::MAX), + ("-2147483648", 10, i32::MIN), + ("2147483647", 10, i32::MAX), + ("-zik0zk", 36, i32::MIN), + ("zik0zj", 36, i32::MAX), + ] { + let text = JavaLangString::from_rust_string(&jvm, text).await?; + let parsed: i32 = jvm + .invoke_static("java/lang/Integer", "parseInt", "(Ljava/lang/String;I)I", (text, radix)) + .await?; + assert_eq!(parsed, expected); + } + + let decoded_name = JavaLangString::from_rust_string(&jvm, "-0x80000000").await?; + let decoded: ClassInstanceRef = jvm + .invoke_static("java/lang/Integer", "decode", "(Ljava/lang/String;)Ljava/lang/Integer;", (decoded_name,)) + .await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&decoded, "intValue", "()I", ()).await?, i32::MIN); + + for (method, expected) in [ + ("toBinaryString", "11111111111111111111111111111111"), + ("toOctalString", "37777777777"), + ("toHexString", "ffffffff"), + ] { + let formatted: ClassInstanceRef = jvm.invoke_static("java/lang/Integer", method, "(I)Ljava/lang/String;", (-1,)).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &formatted).await?, expected); + } + + let left = jvm.new_class("java/lang/Integer", "(I)V", (1,)).await?; + let right = jvm.new_class("java/lang/Integer", "(I)V", (2,)).await?; + assert_eq!( + jvm.invoke_virtual::<_, i32>(&left, "compareTo", "(Ljava/lang/Integer;)I", (right.clone(),)) + .await?, + -1 + ); + assert_eq!( + jvm.invoke_virtual::<_, i32>(&left, "compareTo", "(Ljava/lang/Object;)I", (right.clone(),)) + .await?, + -1 + ); + assert!( + !jvm.invoke_virtual::<_, bool>(&left, "equals", "(Ljava/lang/Object;)Z", (right.clone(),)) + .await? + ); + assert_eq!(jvm.invoke_virtual::<_, i32>(&left, "hashCode", "()I", ()).await?, 1); + + let typed_null_result: Result = jvm.invoke_virtual(&left, "compareTo", "(Ljava/lang/Integer;)I", (None,)).await; + let Err(JavaError::JavaException(exception)) = typed_null_result else { + panic!("Integer typed compare null must throw NPE"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + + let null_result: Result = jvm.invoke_virtual(&left, "compareTo", "(Ljava/lang/Object;)I", (None,)).await; + let Err(JavaError::JavaException(exception)) = null_result else { + panic!("Integer raw compare null must throw NPE"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + + let object = jvm.new_class("java/lang/Object", "()V", ()).await?; + let wrong_result: Result = jvm.invoke_virtual(&left, "compareTo", "(Ljava/lang/Object;)I", (object,)).await; + let Err(JavaError::JavaException(exception)) = wrong_result else { + panic!("Integer raw compare wrong type must throw CCE"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/ClassCastException")); + + let min = jvm.get_static_field::("java/lang/Integer", "MIN_VALUE", "I").await?; + let max = jvm.get_static_field::("java/lang/Integer", "MAX_VALUE", "I").await?; + assert_eq!((min, max), (i32::MIN, i32::MAX)); + let typ = jvm.get_static_field("java/lang/Integer", "TYPE", "Ljava/lang/Class;").await?; + let name: ClassInstanceRef = jvm.invoke_virtual(&typ, "getName", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &name).await?, "int"); + assert!(jvm.invoke_virtual::<_, bool>(&typ, "isPrimitive", "()Z", ()).await?); + let _: () = jvm.invoke_static("java/lang/System", "gc", "()V", ()).await?; + let typ = jvm.get_static_field("java/lang/Integer", "TYPE", "Ljava/lang/Class;").await?; + assert!(jvm.invoke_virtual::<_, bool>(&typ, "isPrimitive", "()Z", ()).await?); + Ok(()) +} + +#[tokio::test] +async fn test_integer_rejects_invalid_forms_and_uses_property_defaults() -> Result<()> { + let jvm = test_jvm().await?; + + for (text, radix) in [ + ("", 10), + ("+", 10), + ("-", 10), + (" 1", 10), + ("2147483648", 10), + ("-2147483649", 10), + ("1", 1), + ("1", 37), + ] { + let text = JavaLangString::from_rust_string(&jvm, text).await?; + let result: Result = jvm + .invoke_static("java/lang/Integer", "parseInt", "(Ljava/lang/String;I)I", (text, radix)) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("Integer.parseInt must reject invalid input"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NumberFormatException")); + } + + let misplaced_sign = JavaLangString::from_rust_string(&jvm, "0x-1").await?; + let result: Result> = jvm + .invoke_static( + "java/lang/Integer", + "decode", + "(Ljava/lang/String;)Ljava/lang/Integer;", + (misplaced_sign,), + ) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("Integer.decode must reject a sign after the radix prefix"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NumberFormatException")); + + for (text, expected) in [("#7f", 127i32), ("0177", 127i32)] { + let text = JavaLangString::from_rust_string(&jvm, text).await?; + let decoded: ClassInstanceRef = jvm + .invoke_static("java/lang/Integer", "decode", "(Ljava/lang/String;)Ljava/lang/Integer;", (text,)) + .await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&decoded, "intValue", "()I", ()).await?, expected); + } + + let key = JavaLangString::from_rust_string(&jvm, "rustjava.test.integer").await?; + let value = JavaLangString::from_rust_string(&jvm, "0x2a").await?; + let _: ClassInstanceRef = jvm + .invoke_static( + "java/lang/System", + "setProperty", + "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Object;", + (key.clone(), value), + ) + .await?; + let property: ClassInstanceRef = jvm + .invoke_static( + "java/lang/Integer", + "getInteger", + "(Ljava/lang/String;)Ljava/lang/Integer;", + (key.clone(),), + ) + .await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&property, "intValue", "()I", ()).await?, 42); + + let invalid = JavaLangString::from_rust_string(&jvm, "not-an-integer").await?; + let _: ClassInstanceRef = jvm + .invoke_static( + "java/lang/System", + "setProperty", + "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Object;", + (key.clone(), invalid), + ) + .await?; + let property: ClassInstanceRef = jvm + .invoke_static( + "java/lang/Integer", + "getInteger", + "(Ljava/lang/String;I)Ljava/lang/Integer;", + (key.clone(), 17), + ) + .await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&property, "intValue", "()I", ()).await?, 17); + + let default = jvm.new_class("java/lang/Integer", "(I)V", (23,)).await?; + let property: ClassInstanceRef = jvm + .invoke_static( + "java/lang/Integer", + "getInteger", + "(Ljava/lang/String;Ljava/lang/Integer;)Ljava/lang/Integer;", + (key, default), + ) + .await?; + assert_eq!(jvm.invoke_virtual::<_, i32>(&property, "intValue", "()I", ()).await?, 23); + + Ok(()) +} diff --git a/java_runtime/tests/classes/java/lang/test_long.rs b/java_runtime/tests/classes/java/lang/test_long.rs new file mode 100644 index 00000000..02eb73e9 --- /dev/null +++ b/java_runtime/tests/classes/java/lang/test_long.rs @@ -0,0 +1,168 @@ +use java_runtime::classes::java::lang::{Long, Object, String}; +use jvm::{ClassInstanceRef, JavaError, Result, runtime::JavaLangString}; + +use test_utils::test_jvm; + +#[tokio::test] +async fn test_long_api() -> Result<()> { + let jvm = test_jvm().await?; + let value = jvm.new_class("java/lang/Long", "(J)V", (i64::MIN,)).await?; + assert_eq!(jvm.invoke_virtual::<_, i8>(&value, "byteValue", "()B", ()).await?, 0); + assert_eq!(jvm.invoke_virtual::<_, i16>(&value, "shortValue", "()S", ()).await?, 0); + assert_eq!(jvm.invoke_virtual::<_, i64>(&value, "longValue", "()J", ()).await?, i64::MIN); + assert_eq!(jvm.invoke_virtual::<_, i32>(&value, "intValue", "()I", ()).await?, 0); + assert_eq!(jvm.invoke_virtual::<_, f32>(&value, "floatValue", "()F", ()).await?, i64::MIN as f32); + assert_eq!(jvm.invoke_virtual::<_, f64>(&value, "doubleValue", "()D", ()).await?, i64::MIN as f64); + + for (input, radix, expected) in [ + ("7fffffffffffffff", 16, i64::MAX), + ("-8000000000000000", 16, i64::MIN), + ("\u{ff21}", 16, 10), + ("\u{0661}\u{0662}\u{0663}", 10, 123), + ] { + let string = JavaLangString::from_rust_string(&jvm, input).await?; + let parsed: i64 = jvm + .invoke_static("java/lang/Long", "parseLong", "(Ljava/lang/String;I)J", (string, radix)) + .await?; + assert_eq!(parsed, expected); + } + + let value = jvm + .invoke_static::<_, jvm::ClassInstanceRef>( + "java/lang/Long", + "valueOf", + "(Ljava/lang/String;)Ljava/lang/Long;", + (JavaLangString::from_rust_string(&jvm, "-1").await?,), + ) + .await?; + let hex: jvm::ClassInstanceRef = jvm + .invoke_static("java/lang/Long", "toHexString", "(J)Ljava/lang/String;", (-1i64,)) + .await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &hex).await?, "ffffffffffffffff"); + assert_eq!(jvm.invoke_virtual::<_, i64>(&value, "longValue", "()J", ()).await?, -1); + + let overflow = JavaLangString::from_rust_string(&jvm, "9223372036854775808").await?; + let result: Result = jvm + .invoke_static("java/lang/Long", "parseLong", "(Ljava/lang/String;)J", (overflow,)) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("Long.parseLong must reject overflow"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NumberFormatException")); + + let typed_null_result: Result = jvm.invoke_virtual(&value, "compareTo", "(Ljava/lang/Long;)I", (None,)).await; + let Err(JavaError::JavaException(exception)) = typed_null_result else { + panic!("Long typed compare null must throw NPE"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + + let misplaced_sign = JavaLangString::from_rust_string(&jvm, "#-1").await?; + let result: Result> = jvm + .invoke_static("java/lang/Long", "decode", "(Ljava/lang/String;)Ljava/lang/Long;", (misplaced_sign,)) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("Long.decode must reject a sign after the radix prefix"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NumberFormatException")); + + let key = JavaLangString::from_rust_string(&jvm, "rustjava.test.long").await?; + let property_value = JavaLangString::from_rust_string(&jvm, "077").await?; + let _: ClassInstanceRef = jvm + .invoke_static( + "java/lang/System", + "setProperty", + "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Object;", + (key.clone(), property_value), + ) + .await?; + let property: ClassInstanceRef = jvm + .invoke_static("java/lang/Long", "getLong", "(Ljava/lang/String;)Ljava/lang/Long;", (key.clone(),)) + .await?; + assert_eq!(jvm.invoke_virtual::<_, i64>(&property, "longValue", "()J", ()).await?, 63); + + let invalid = JavaLangString::from_rust_string(&jvm, "invalid").await?; + let _: ClassInstanceRef = jvm + .invoke_static( + "java/lang/System", + "setProperty", + "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Object;", + (key.clone(), invalid), + ) + .await?; + let property: ClassInstanceRef = jvm + .invoke_static("java/lang/Long", "getLong", "(Ljava/lang/String;J)Ljava/lang/Long;", (key, -9i64)) + .await?; + assert_eq!(jvm.invoke_virtual::<_, i64>(&property, "longValue", "()J", ()).await?, -9); + + for (text, radix, expected) in [ + ("-1000000000000000000000000000000000000000000000000000000000000000", 2, i64::MIN), + ("111111111111111111111111111111111111111111111111111111111111111", 2, i64::MAX), + ("-9223372036854775808", 10, i64::MIN), + ("9223372036854775807", 10, i64::MAX), + ("-8000000000000000", 16, i64::MIN), + ("7fffffffffffffff", 16, i64::MAX), + ("-1y2p0ij32e8e8", 36, i64::MIN), + ("1y2p0ij32e8e7", 36, i64::MAX), + ] { + let text = JavaLangString::from_rust_string(&jvm, text).await?; + let parsed: i64 = jvm + .invoke_static("java/lang/Long", "parseLong", "(Ljava/lang/String;I)J", (text, radix)) + .await?; + assert_eq!(parsed, expected); + } + for text in ["-9223372036854775809", "9223372036854775808", "", "+", "-"] { + let text = JavaLangString::from_rust_string(&jvm, text).await?; + let result: Result = jvm.invoke_static("java/lang/Long", "parseLong", "(Ljava/lang/String;)J", (text,)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("Long.parseLong must reject invalid input"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NumberFormatException")); + } + + for (text, expected) in [("#7f", 127i64), ("077", 63i64), ("-0x8000000000000000", i64::MIN)] { + let text = JavaLangString::from_rust_string(&jvm, text).await?; + let decoded: ClassInstanceRef = jvm + .invoke_static("java/lang/Long", "decode", "(Ljava/lang/String;)Ljava/lang/Long;", (text,)) + .await?; + assert_eq!(jvm.invoke_virtual::<_, i64>(&decoded, "longValue", "()J", ()).await?, expected); + } + for (method, expected) in [ + ("toBinaryString", "1111111111111111111111111111111111111111111111111111111111111111"), + ("toOctalString", "1777777777777777777777"), + ("toHexString", "ffffffffffffffff"), + ] { + let text: ClassInstanceRef = jvm.invoke_static("java/lang/Long", method, "(J)Ljava/lang/String;", (-1i64,)).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &text).await?, expected); + } + + let equal = jvm.new_class("java/lang/Long", "(J)V", (-1i64,)).await?; + assert!( + jvm.invoke_virtual::<_, bool>(&value, "equals", "(Ljava/lang/Object;)Z", (equal.clone(),)) + .await? + ); + assert_eq!(jvm.invoke_virtual::<_, i32>(&value, "hashCode", "()I", ()).await?, 0); + assert_eq!( + jvm.invoke_virtual::<_, i32>(&value, "compareTo", "(Ljava/lang/Object;)I", (equal,)) + .await?, + 0 + ); + let object = jvm.new_class("java/lang/Object", "()V", ()).await?; + let result: Result = jvm.invoke_virtual(&value, "compareTo", "(Ljava/lang/Object;)I", (object,)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("Long raw compare must reject another type"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/ClassCastException")); + let result: Result = jvm.invoke_virtual(&value, "compareTo", "(Ljava/lang/Object;)I", (None,)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("Long raw compare null must throw NPE"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + + assert_eq!(jvm.get_static_field::("java/lang/Long", "MIN_VALUE", "J").await?, i64::MIN); + assert_eq!(jvm.get_static_field::("java/lang/Long", "MAX_VALUE", "J").await?, i64::MAX); + let typ = jvm.get_static_field("java/lang/Long", "TYPE", "Ljava/lang/Class;").await?; + let type_name: ClassInstanceRef = jvm.invoke_virtual(&typ, "getName", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &type_name).await?, "long"); + assert!(jvm.invoke_virtual::<_, bool>(&typ, "isPrimitive", "()Z", ()).await?); + Ok(()) +} diff --git a/java_runtime/tests/classes/java/lang/test_number.rs b/java_runtime/tests/classes/java/lang/test_number.rs new file mode 100644 index 00000000..62f19932 --- /dev/null +++ b/java_runtime/tests/classes/java/lang/test_number.rs @@ -0,0 +1,76 @@ +use java_constants::{ClassAccessFlags, MethodAccessFlags}; +use java_runtime::classes::java::lang::{Comparable, Number}; +use jvm::{JavaError, Result}; + +use test_utils::test_jvm; + +#[tokio::test] +async fn test_number_is_abstract() -> Result<()> { + let jvm = test_jvm().await?; + + let number_proto = Number::as_proto(); + assert!(number_proto.access_flags.contains(ClassAccessFlags::PUBLIC | ClassAccessFlags::ABSTRACT)); + for method in &number_proto.methods { + assert!(method.access_flags.contains(MethodAccessFlags::PUBLIC)); + if method.name != "" && method.name != "byteValue" && method.name != "shortValue" { + assert!(method.access_flags.contains(MethodAccessFlags::ABSTRACT)); + } + } + + let comparable_proto = Comparable::as_proto(); + assert!( + comparable_proto + .access_flags + .contains(ClassAccessFlags::PUBLIC | ClassAccessFlags::INTERFACE | ClassAccessFlags::ABSTRACT) + ); + assert!( + comparable_proto.methods[0] + .access_flags + .contains(MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT) + ); + + let number_class = jvm.resolve_class("java/lang/Number").await?.java_class(); + let serializable_class = jvm.resolve_class("java/io/Serializable").await?.java_class(); + let is_serializable: bool = jvm + .invoke_virtual(&serializable_class, "isAssignableFrom", "(Ljava/lang/Class;)Z", (number_class.clone(),)) + .await?; + assert!(is_serializable); + let comparable_class = jvm.resolve_class("java/lang/Comparable").await?.java_class(); + + let integer = jvm.new_class("java/lang/Integer", "(I)V", (257,)).await?; + assert_eq!(jvm.invoke_virtual::<_, i8>(&integer, "byteValue", "()B", ()).await?, 1); + assert_eq!(jvm.invoke_virtual::<_, i16>(&integer, "shortValue", "()S", ()).await?, 257); + let negative = jvm.new_class("java/lang/Integer", "(I)V", (-129,)).await?; + assert_eq!(jvm.invoke_virtual::<_, i8>(&negative, "byteValue", "()B", ()).await?, 127); + + for class_name in [ + "java/lang/Byte", + "java/lang/Short", + "java/lang/Integer", + "java/lang/Long", + "java/lang/Float", + "java/lang/Double", + ] { + let wrapper_class = jvm.resolve_class(class_name).await?.java_class(); + let is_number: bool = jvm + .invoke_virtual(&number_class, "isAssignableFrom", "(Ljava/lang/Class;)Z", (wrapper_class.clone(),)) + .await?; + assert!(is_number); + let is_serializable: bool = jvm + .invoke_virtual(&serializable_class, "isAssignableFrom", "(Ljava/lang/Class;)Z", (wrapper_class.clone(),)) + .await?; + assert!(is_serializable); + let is_comparable: bool = jvm + .invoke_virtual(&comparable_class, "isAssignableFrom", "(Ljava/lang/Class;)Z", (wrapper_class,)) + .await?; + assert!(is_comparable); + } + + let result = jvm.new_class("java/lang/Number", "()V", ()).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("Number must not be instantiable"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/InstantiationError")); + + Ok(()) +} diff --git a/java_runtime/tests/classes/java/lang/test_short.rs b/java_runtime/tests/classes/java/lang/test_short.rs new file mode 100644 index 00000000..91be27a0 --- /dev/null +++ b/java_runtime/tests/classes/java/lang/test_short.rs @@ -0,0 +1,107 @@ +use java_runtime::classes::java::lang::String; +use jvm::{ClassInstanceRef, JavaError, Result, runtime::JavaLangString}; + +use test_utils::test_jvm; + +#[tokio::test] +async fn test_short_api() -> Result<()> { + let jvm = test_jvm().await?; + let value = jvm.new_class("java/lang/Short", "(S)V", (-129i16,)).await?; + assert_eq!(jvm.invoke_virtual::<_, i8>(&value, "byteValue", "()B", ()).await?, 127); + assert_eq!(jvm.invoke_virtual::<_, i16>(&value, "shortValue", "()S", ()).await?, -129); + assert_eq!(jvm.invoke_virtual::<_, i32>(&value, "intValue", "()I", ()).await?, -129); + assert_eq!(jvm.invoke_virtual::<_, i64>(&value, "longValue", "()J", ()).await?, -129); + assert_eq!(jvm.invoke_virtual::<_, f32>(&value, "floatValue", "()F", ()).await?, -129.0); + assert_eq!(jvm.invoke_virtual::<_, f64>(&value, "doubleValue", "()D", ()).await?, -129.0); + + for (input, radix, expected) in [("7f", 16, 127i16), ("-100000", 2, -32i16)] { + let string = JavaLangString::from_rust_string(&jvm, input).await?; + let parsed: i16 = jvm + .invoke_static("java/lang/Short", "parseShort", "(Ljava/lang/String;I)S", (string, radix)) + .await?; + assert_eq!(parsed, expected); + } + + let min = jvm.get_static_field::("java/lang/Short", "MIN_VALUE", "S").await?; + let max = jvm.get_static_field::("java/lang/Short", "MAX_VALUE", "S").await?; + assert_eq!((min, max), (i16::MIN, i16::MAX)); + let name = JavaLangString::from_rust_string(&jvm, "077").await?; + let decoded = jvm + .invoke_static("java/lang/Short", "decode", "(Ljava/lang/String;)Ljava/lang/Short;", (name,)) + .await?; + assert_eq!(jvm.invoke_virtual::<_, i16>(&decoded, "shortValue", "()S", ()).await?, 63); + + let null_result: Result = jvm.invoke_static("java/lang/Short", "parseShort", "(Ljava/lang/String;)S", (None,)).await; + let Err(JavaError::JavaException(exception)) = null_result else { + panic!("Short.parseShort(null) must reject null"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NumberFormatException")); + + let result: Result = jvm.invoke_virtual(&value, "compareTo", "(Ljava/lang/Short;)I", (None,)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("Short typed compare null must throw NPE"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + + for (text, radix, expected) in [ + ("-1000000000000000", 2, i16::MIN), + ("111111111111111", 2, i16::MAX), + ("-32768", 10, i16::MIN), + ("32767", 10, i16::MAX), + ("-8000", 16, i16::MIN), + ("7fff", 16, i16::MAX), + ("-pa8", 36, i16::MIN), + ("pa7", 36, i16::MAX), + ] { + let text = JavaLangString::from_rust_string(&jvm, text).await?; + let parsed: i16 = jvm + .invoke_static("java/lang/Short", "parseShort", "(Ljava/lang/String;I)S", (text, radix)) + .await?; + assert_eq!(parsed, expected); + } + for text in ["-32769", "32768"] { + let text = JavaLangString::from_rust_string(&jvm, text).await?; + let result: Result = jvm.invoke_static("java/lang/Short", "parseShort", "(Ljava/lang/String;)S", (text,)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("Short.parseShort must reject overflow"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NumberFormatException")); + } + + let equal = jvm.new_class("java/lang/Short", "(S)V", (-129i16,)).await?; + assert!( + jvm.invoke_virtual::<_, bool>(&value, "equals", "(Ljava/lang/Object;)Z", (equal.clone(),)) + .await? + ); + assert_eq!(jvm.invoke_virtual::<_, i32>(&value, "hashCode", "()I", ()).await?, -129); + assert_eq!( + jvm.invoke_virtual::<_, i32>(&value, "compareTo", "(Ljava/lang/Object;)I", (equal,)) + .await?, + 0 + ); + let object = jvm.new_class("java/lang/Object", "()V", ()).await?; + let result: Result = jvm.invoke_virtual(&value, "compareTo", "(Ljava/lang/Object;)I", (object,)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("Short raw compare must reject another type"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/ClassCastException")); + let result: Result = jvm.invoke_virtual(&value, "compareTo", "(Ljava/lang/Object;)I", (None,)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("Short raw compare null must throw NPE"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + + for (text, expected) in [("#7fff", i16::MAX), ("077777", i16::MAX)] { + let text = JavaLangString::from_rust_string(&jvm, text).await?; + let decoded = jvm + .invoke_static("java/lang/Short", "decode", "(Ljava/lang/String;)Ljava/lang/Short;", (text,)) + .await?; + assert_eq!(jvm.invoke_virtual::<_, i16>(&decoded, "shortValue", "()S", ()).await?, expected); + } + + let typ = jvm.get_static_field("java/lang/Short", "TYPE", "Ljava/lang/Class;").await?; + let type_name: ClassInstanceRef = jvm.invoke_virtual(&typ, "getName", "()Ljava/lang/String;", ()).await?; + assert_eq!(JavaLangString::to_rust_string(&jvm, &type_name).await?, "short"); + assert!(jvm.invoke_virtual::<_, bool>(&typ, "isPrimitive", "()Z", ()).await?); + Ok(()) +} diff --git a/jvm/src/runtime/java_lang_class.rs b/jvm/src/runtime/java_lang_class.rs index 012f68c7..7d0da194 100644 --- a/jvm/src/runtime/java_lang_class.rs +++ b/jvm/src/runtime/java_lang_class.rs @@ -7,12 +7,30 @@ use crate::{Array, ClassInstanceRef, Result, class_definition::ClassDefinition, pub struct JavaLangClass; impl JavaLangClass { + pub async fn from_rust_primitive(jvm: &Jvm, name: &str) -> Result> { + let mut java_class = jvm.new_class("java/lang/Class", "()V", ()).await?; + let mut name_bytes = jvm.instantiate_array("B", name.len()).await?; + let bytes: Vec = cast_vec(name.as_bytes().to_vec()); + jvm.store_array(&mut name_bytes, 0, bytes).await?; + jvm.put_field(&mut java_class, "nameBytes", "[B", name_bytes).await?; + + Ok(java_class) + } + #[allow(clippy::borrowed_box)] - pub async fn to_rust_class(jvm: &Jvm, this: &Box) -> Result> { + pub async fn name(jvm: &Jvm, this: &Box) -> Result { 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(); + match String::from_utf8(cast_vec(name_bytes_vec)) { + Ok(name) => Ok(name), + Err(_) => Err(jvm.exception("java/lang/NoClassDefFoundError", "invalid class name").await), + } + } + + #[allow(clippy::borrowed_box)] + pub async fn to_rust_class(jvm: &Jvm, this: &Box) -> Result> { + let class_name = Self::name(jvm, this).await?; if let Some(class) = jvm.get_class(&class_name) { Ok(class.definition) } else { From 1f356aee49b6e48fa3023b381159c9d27057d00f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 16 Jul 2026 19:56:49 +0000 Subject: [PATCH 5/6] Bump tokio from 1.52.3 to 1.52.4 (#179) Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.52.3 to 1.52.4. - [Release notes](https://github.com/tokio-rs/tokio/releases) - [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.52.3...tokio-1.52.4) --- updated-dependencies: - dependency-name: tokio dependency-version: 1.52.4 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4ea10b81..dc2d1196 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -669,9 +669,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.52.3" +version = "1.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" +checksum = "317fafbbe3f02fc663dad00ea6186197de963cd4190e86a26d8d0fae095539af" dependencies = [ "pin-project-lite", "tokio-macros", From 7ed27b518d4e0486e93d92a24ecd9084460aeb55 Mon Sep 17 00:00:00 2001 From: jun0 Date: Mon, 17 Aug 2026 13:20:00 +0900 Subject: [PATCH 6/6] [rustjava-upstream-sync-s1-tracing-cut-1f356ae] docs: record S1 landing (conflicts 2, setProperty descriptor breakage) --- REPORT.md | 19 +++++++++++++++++++ STATE.md | 25 +++++++++++++++++++++---- 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/REPORT.md b/REPORT.md index fcd53192..9654adf2 100644 --- a/REPORT.md +++ b/REPORT.md @@ -1,5 +1,24 @@ # REPORT +## [2026-08-17] upstream ๋™๊ธฐ S1 โ€” ์ปท `1f356ae` ๋จธ์ง€ (rustjava-upstream-sync-s1-tracing-cut-1f356ae) +- ๋ฌด์—‡์„: upstream `1f356ae`(#173~#179 ยท 5์ปค๋ฐ‹)๋ฅผ ๋จธ์ง€ํ–ˆ๋‹ค. ์ถฉ๋Œ **2** ํ•ด์†Œ โ€” + `lang.rs` ๋Š” **์–‘์ชฝ ๋ณ‘ํ•ฉ**(์šฐ๋ฆฌ `class_format_error` + upstream ์˜ Java 1.2 wrapper 9์ข…), + `thread.rs` ๋Š” **upstream ๋ผˆ๋Œ€ + PR #4 ์ˆ˜๋™ span ์žฌ์ ์šฉ**(`#[tracing::instrument]` ํ•œ ์ค„๋งŒ ์น˜ํ™˜, + `Cargo.toml` 2๊ฐœ ๋ฌด์ ‘์ด‰). 66ํŒŒ์ผ `+5,235 / โˆ’151`. +- ์™œ: ์ ‘๊ทผ์•ˆ ยง6 ์ด ์ •ํ•œ 7ํšŒ์ฐจ ์ค‘ ์ฒซ ํšŒ์ฐจ์ด๊ณ  ์ถ•์€ tracing ์ด๋‹ค. upstream `thread.rs` ๋ฅผ ๊ทธ๋Œ€๋กœ ์ทจํ•˜๋ฉด + `attributes` ํ”ผ์ฒ˜๊ฐ€ ๊บผ์ง„ tracing ์— ์†์„ฑ ๋งคํฌ๋กœ๊ฐ€ ๊ฑธ๋ ค **์ปดํŒŒ์ผ์ด ๊นจ์ง€๊ณ **, ํ”ผ์ฒ˜๋ฅผ ๋˜์‚ด๋ฆฌ๋ฉด PR #4 ๊ฐ€ + ํ†ต์งธ๋กœ ๋˜๋Œ์•„๊ฐ„๋‹ค. ๋ผˆ๋Œ€๋งŒ ์ทจํ•˜๊ณ  span ๋งŒ ์ˆ˜๋™์œผ๋กœ ๋˜๋Œ๋ ค ๋‘˜ ๋‹ค ํ”ผํ–ˆ๋‹ค. + โ˜…**์ถฉ๋Œ ๋ชฉ๋ก ๋ฐ–์—์„œ ํ•˜๋‚˜๊ฐ€ ๋” ๊นจ์กŒ๋‹ค**: ์šฐ๋ฆฌ PR #5 ๊ฐ€ JDK ๊ทœ๊ฒฉ์— ๋งž๊ฒŒ ๊ณ ์นœ + `System.setProperty` ์„œ์ˆ ์ž(`โ€ฆ)Ljava/lang/String;` โ€” ์‹ค์ œ javac ๋ฐ”์ดํŠธ์ฝ”๋“œ๊ฐ€ ๊ทธ๋ ‡๋‹ค)์™€ + upstream ์˜ ๊ตฌํŒ(`โ€ฆ)Ljava/lang/Object;`)์ด ์–ด๊ธ‹๋‚˜, upstream ์ด ์ƒˆ๋กœ ๋“ค์—ฌ์˜จ wrapper ํ…Œ์ŠคํŠธ 3๊ฑด์ด + `NoSuchMethodError` ๋กœ ์ฃฝ์—ˆ๋‹ค. ์šฐ๋ฆฌ ์„œ์ˆ ์ž๋ฅผ ์œ ์ง€ํ•˜๊ณ  upstream ํ…Œ์ŠคํŠธ ํ˜ธ์ถœ๋ถ€ 6๊ณณ์„ ๊ณ ์ณค๋‹ค. +- ์‚ฌ์šฉ์ž ์˜ํ–ฅ: ์—†์Œ(๋™์ž‘ ๋ณ€๊ฒฝ 0). Java 1.2 wrapper ํด๋ž˜์Šค 9์ข… + (`Boolean`/`Byte`/`Character`/`Double`/`Float`/`Long`/`Number`/`Short` ยท `ClassNotFoundException`)๊ณผ + `Thread.currentThread()` ๋™์ผ๊ฐ์ฒด ๋ฐ˜ํ™˜์ด ๋“ค์–ด์™”๋‹ค. `cargo test --all` **169 passed / 0 failed / 1 ignored** + (๊ธฐ์ค€์„  149 โ†’ +20, ์ „๋ถ€ upstream ์‹ ๊ทœ + ์šฐ๋ฆฌ ๊ธฐ์กด๋ถ„). +- ํ›„์† ์ถ”์ฒœ: S2(์ปท `af4f6f8` ยท charset ์ถ• ยท ์ƒˆ ์ถฉ๋Œ +5). โ˜…์ฐฉ์ˆ˜ ์‹œ ์ถฉ๋Œ ์žฌ์ธก์ • ํ•„์ˆ˜ ยท + โ˜…**์šฐ๋ฆฌ ํ”„๋กœ๋•์…˜ ์„œ์ˆ ์ž/์‹œ๊ทธ๋‹ˆ์ฒ˜ ๋ณ€๊ฒฝ์ด upstream ์‹ ๊ทœ ํ…Œ์ŠคํŠธ์™€ ์–ด๊ธ‹๋‚˜๋Š”์ง€**๋ฅผ S1 ๊ณผ ๊ฐ™์€ ๋ฐฉ์‹์œผ๋กœ ํ›‘์–ด๋ผ. + ## [2026-08-16] upstream ๋™๊ธฐํ™” ์ ‘๊ทผ์•ˆ ํ™•์ • (rustjava-upstream-sync-approach-plan) - ๋ฌด์—‡์„: ๊ฒฉ์ฐจ๋ฅผ ์˜ค๋Š˜ ๊ฐ’์œผ๋กœ ๋‹ค์‹œ ์žฌ๊ณ (**10 ์•ž์„ฌ / 33 ๋’ค์ฒ˜์ง** ยท ์ถฉ๋Œ **17 โ†’ 19ํŒŒ์ผ**), ์ถฉ๋Œ 19ํŒŒ์ผ์„ ์ฒ˜๋ถ„ ์–ดํœ˜ 4์ข…์œผ๋กœ ๋ถ„๋ฅ˜ํ•œ ํ‘œ์™€ ๋‹จ๊ณ„ ๋ถ„ํ• ์•ˆ์„ `docs/upstream-sync-approach.md` ๋กœ ํ™•์ •ํ–ˆ๋‹ค. diff --git a/STATE.md b/STATE.md index 7c328bde..ebf539ad 100644 --- a/STATE.md +++ b/STATE.md @@ -1,7 +1,8 @@ # STATE ## ์ง„ํ–‰์ค‘ -- (์—†์Œ) +- [rustjava-upstream-sync-s1-tracing-cut-1f356ae] upstream ์ปท `1f356ae` ๋จธ์ง€ ์™„๋ฃŒ(์ถฉ๋Œ 2 ํ•ด์†Œ ยท + tracing ์ถ•). green ์ „๊ฑด rc=0 ยท 169 passed. **PR ๋Œ€๊ธฐ โ€” ๊ฒŒ์ดํŠธโ‘ข ๋ฏธ์ฐฉ์ง€.** ## ์™„๋ฃŒ - [rustjava-runtime-time-todo-impl] RuntimeImpl ์‹œ๊ฐ„ API `todo!()` 3๊ฑด ์ œ๊ฑฐ(now/sleep/yield) + @@ -29,9 +30,25 @@ ## ๋‹ค์Œ -### โ‘ (์ตœ์šฐ์„ ) upstream ๋™๊ธฐํ™” โ€” โ˜…**์ ‘๊ทผ์•ˆ ํ™•์ •(2026-08-16)**. ์ •๋ณธ = `docs/upstream-sync-approach.md` - -โ˜…**์„ค๊ณ„๋Š” ๋๋‚ฌ๋‹ค. ๋‹ค์Œ์€ ์‹คํ–‰์ด๋‹ค** โ€” ์•„๋ž˜๋Š” ๊ทธ ๋ฌธ์„œ์˜ ์š”์•ฝ์ด๊ณ , ์ฐฉ์ˆ˜ ์ „ **๋ฌธ์„œ๋ฅผ ์ฝ์–ด๋ผ**. +### โ‘ (์ตœ์šฐ์„ ) upstream ๋™๊ธฐํ™” โ€” โ˜…**S1 ์ฐฉ์ง€ ๋Œ€๊ธฐ(2026-08-17)**. ์ •๋ณธ = `docs/upstream-sync-approach.md` + +โ˜…**S1(`1f356ae` ยท tracing ์ถ•)์€ ๋จธ์ง€ ์™„๋ฃŒ ยท PR ๋Œ€๊ธฐ ์ค‘์ด๋‹ค. ๋‹ค์Œ์€ S2(`af4f6f8` ยท charset ์ถ•).** + +**S1 ์‹ค์ธก(2026-08-17 13:1x)**: `merge-tree` ์ถฉ๋Œ **2 ๊ทธ๋Œ€๋กœ**(`lang.rs`ยท`thread.rs`) โ€” ๊ณ„ํš์„œ ์˜ˆ์ธก๊ณผ ์ผ์น˜. +green ์ „๊ฑด rc=0 ยท `cargo test --all` **169 passed / 0 failed / 1 ignored**. +โ˜…**๊ณ„ํš์„œ๊ฐ€ ์ด๋ฆ„ ๋ถ™์ธ 3์œ„ํ—˜ ์ค‘ S1 ์—์„œ ์‹ค์ œ๋กœ ํ„ฐ์ง„ ๊ฒƒ์€ tracing ํ•˜๋‚˜๋ฟ**์ด๋‹ค โ€” +`tests/test_class_format.rs` 4/4 ํ†ต๊ณผ(upstream `classfile/src/error.rs` ์žฌ์ž‘์„ฑ์€ S3 ์ปท `822504b` ์— ์˜จ๋‹ค) ยท +`charset.rs` ํ˜ธ์ถœ์ž 2๊ฑด ์ƒ์กด(clippy green). +โ˜…โ˜…**๋Œ€์‹  ๊ณ„ํš์„œ๊ฐ€ ยซ๋ชฐ๋ž๋˜ยป ํŒŒ์†์ด ํ•˜๋‚˜ ๋‚˜์™”๋‹ค โ€” `java/lang/System.setProperty` ์„œ์ˆ ์ž**: +์šฐ๋ฆฌ PR #5 ๊ฐ€ `โ€ฆ)Ljava/lang/String;` ๋กœ ๊ณ ์ณค๊ณ (**์‹ค์ œ javac ๋ฐ”์ดํŠธ์ฝ”๋“œ** +`test_data/UnsupportedCharset.class` ์ƒ์ˆ˜ํ’€์ด ๊ทธ ์„œ์ˆ ์ž๋‹ค โ€” JDK ๊ทœ๊ฒฉ์ƒ ์šฐ๋ฆฌ๊ฐ€ ์˜ณ๋‹ค), +upstream ์€ ์—ฌ์ „ํžˆ `โ€ฆ)Ljava/lang/Object;` ๋‹ค. ์ถฉ๋Œ 0์œผ๋กœ ์šฐ๋ฆฌ ์ชฝ์ด ๋จธ์ง€๋˜๋Š”๋ฐ upstream PR #176 ์ด +์ƒˆ๋กœ ๋“ค์—ฌ์˜จ wrapper ํ…Œ์ŠคํŠธ 6๊ฐœ ํ˜ธ์ถœ๋ถ€๊ฐ€ `Object` ์„œ์ˆ ์ž๋ฅผ ๋ฐ•์•„ ๋‘์–ด **`NoSuchMethodError` 3๊ฑด**์ด ๋‚ฌ๋‹ค. +โ‡’ ์šฐ๋ฆฌ ์„œ์ˆ ์ž๋ฅผ ์œ ์ง€ํ•˜๊ณ  **upstream ํ…Œ์ŠคํŠธ ํ˜ธ์ถœ๋ถ€ 6๊ณณ์„ ๊ณ ์ณค๋‹ค.** +โ˜…**๊ตํ›ˆ: ใ€Œ์ถฉ๋Œ ๋ชฉ๋ก ๋ฐ– ํŒŒ์†ใ€์€ ์šฐ๋ฆฌ ยซํ…Œ์ŠคํŠธยป๋งŒ์ด ์•„๋‹ˆ๋ผ ์šฐ๋ฆฌ ยซํ”„๋กœ๋•์…˜ ์„œ์ˆ ์ž ๋ณ€๊ฒฝยป์—์„œ๋„ ๋‚˜์˜จ๋‹ค. +๊ทธ๋ฆฌ๊ณ  ๊ทธ๊ฒƒ์€ upstream ์ด ยซ์•ž์œผ๋กœยป ๋“ค์—ฌ์˜ฌ ํ…Œ์ŠคํŠธ์— ์˜ํ•ด ๋’ค๋Šฆ๊ฒŒ ํ„ฐ์ง„๋‹ค โ€” S2~S7 ์—์„œ๋„ ๊ฐ™์€ ํ˜•ํƒœ๋ฅผ ์˜ˆ์ƒํ•˜๋ผ.** + +์•„๋ž˜๋Š” ์ ‘๊ทผ์•ˆ ๋ฌธ์„œ์˜ ์š”์•ฝ์ด๊ณ , ์ฐฉ์ˆ˜ ์ „ **๋ฌธ์„œ๋ฅผ ์ฝ์–ด๋ผ**. **์žฌ์‹ค์ธก(2026-08-16)**: `rev-list --left-right --count origin/main...upstream/main` โ†’ **`10 33`** (์„ ํ–‰ 08-15 ์˜ `9 32` ๋Š” ๋‚ก์•˜๋‹ค) ยท ๊ณตํ†ต์กฐ์ƒ `62cf0c6` ยท origin tip `85f294a` ยท upstream tip `ba5797b`.