diff --git a/Cargo.lock b/Cargo.lock index 5d8ef91a..fba25156 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" @@ -668,9 +668,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", 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`. 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/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 b7ed46a8..f7ec6e12 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 e705c2ab..f385f8e5 100644 --- a/java_runtime/src/classes/java/lang.rs +++ b/java_runtime/src/classes/java/lang.rs @@ -2,16 +2,22 @@ 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_format_error; mod class_loader; +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 +25,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 +50,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_format_error::ClassFormatError, 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, + array_index_out_of_bounds_exception::ArrayIndexOutOfBoundsException, array_store_exception::ArrayStoreException, boolean::Boolean, byte::Byte, + character::Character, class::Class, class_cast_exception::ClassCastException, class_format_error::ClassFormatError, 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 a8c8e682..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,10 +129,16 @@ 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('.', "/"); - 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/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/classes/java/lang/string.rs b/java_runtime/src/classes/java/lang/string.rs index 477c16a6..9f68591e 100644 --- a/java_runtime/src/classes/java/lang/string.rs +++ b/java_runtime/src/classes/java/lang/string.rs @@ -380,9 +380,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/lang/thread.rs b/java_runtime/src/classes/java/lang/thread.rs index bde4cde5..883b0da4 100644 --- a/java_runtime/src/classes/java/lang/thread.rs +++ b/java_runtime/src/classes/java/lang/thread.rs @@ -96,7 +96,7 @@ impl Thread { async { 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; @@ -210,10 +210,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/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 ca21bb8e..2749714d 100644 --- a/java_runtime/src/loader.rs +++ b/java_runtime/src/loader.rs @@ -37,16 +37,22 @@ 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::ClassFormatError::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(), @@ -54,6 +60,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(), @@ -61,11 +68,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(), @@ -132,6 +141,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/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..39cf180d --- /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/String;", + (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/String;", + (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", ('A' 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..c1ea10c9 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/String;", + (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/String;", + (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..ee8d83de --- /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/String;", + (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/String;", + (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_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/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/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 dd95b589..f461639d 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/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 { 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/src/runtime.rs b/src/runtime.rs index a6570e43..c71d1b4e 100644 --- a/src/runtime.rs +++ b/src/runtime.rs @@ -131,8 +131,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/CurrentThread$1.class b/test_data/CurrentThread$1.class new file mode 100644 index 00000000..a0170410 Binary files /dev/null and b/test_data/CurrentThread$1.class differ diff --git a/test_data/CurrentThread.class b/test_data/CurrentThread.class new file mode 100644 index 00000000..cafa82c1 Binary files /dev/null and b/test_data/CurrentThread.class differ 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/FileErrors.class b/test_data/FileErrors.class new file mode 100644 index 00000000..b137b9e2 Binary files /dev/null and b/test_data/FileErrors.class differ 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 00000000..5b0cd01b Binary files /dev/null and b/test_data/FileLength.class differ 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 00000000..86295c2f Binary files /dev/null and b/test_data/ForName.class differ 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 00000000..4d82419f Binary files /dev/null and b/test_data/SurrogateChars.class differ diff --git a/test_data/SurrogateChars.txt b/test_data/SurrogateChars.txt new file mode 100644 index 00000000..17e48d32 --- /dev/null +++ b/test_data/SurrogateChars.txt @@ -0,0 +1,6 @@ +2 +😀 +1 +1 +? +2 diff --git a/test_data/ZipCorrupt.class b/test_data/ZipCorrupt.class new file mode 100644 index 00000000..37b7442a Binary files /dev/null and b/test_data/ZipCorrupt.class differ diff --git a/test_data/ZipCorrupt.txt b/test_data/ZipCorrupt.txt new file mode 100644 index 00000000..ad53f2c8 --- /dev/null +++ b/test_data/ZipCorrupt.txt @@ -0,0 +1 @@ +zipex 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); + } +} diff --git a/test_data/src/FileErrors.java b/test_data/src/FileErrors.java new file mode 100644 index 00000000..c063027e --- /dev/null +++ b/test_data/src/FileErrors.java @@ -0,0 +1,28 @@ +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.RandomAccessFile; + +public class FileErrors { + public static void main(String[] args) { + try { + new FileInputStream(new File("definitely-missing-file")); + System.out.println("no-ex"); + } catch (FileNotFoundException e) { + System.out.println("fnf-in"); + } + try { + new FileOutputStream(new File("definitely-missing-dir/x")); + System.out.println("no-ex"); + } catch (FileNotFoundException e) { + System.out.println("fnf-out"); + } + try { + new RandomAccessFile("definitely-missing-file", "r"); + System.out.println("no-ex"); + } catch (FileNotFoundException e) { + System.out.println("fnf-raf"); + } + } +} diff --git a/test_data/src/FileLength.java b/test_data/src/FileLength.java new file mode 100644 index 00000000..b395ba62 --- /dev/null +++ b/test_data/src/FileLength.java @@ -0,0 +1,9 @@ +import java.io.File; + +public class FileLength { + public static void main(String[] args) { + File missing = new File("definitely-missing-file"); + System.out.println(missing.exists()); + System.out.println(missing.length()); + } +} diff --git a/test_data/src/ForName.java b/test_data/src/ForName.java new file mode 100644 index 00000000..b1627fac --- /dev/null +++ b/test_data/src/ForName.java @@ -0,0 +1,11 @@ +public class ForName { + public static void main(String[] args) throws Exception { + System.out.println(Class.forName("java.util.Vector").getName()); + try { + Class.forName("no.such.Clazz"); + System.out.println("no-ex"); + } catch (ClassNotFoundException e) { + System.out.println("cnfe"); + } + } +} diff --git a/test_data/src/SurrogateChars.java b/test_data/src/SurrogateChars.java new file mode 100644 index 00000000..54e941a6 --- /dev/null +++ b/test_data/src/SurrogateChars.java @@ -0,0 +1,20 @@ +public class SurrogateChars { + public static void main(String[] args) { + StringBuffer sb = new StringBuffer(); + sb.append('\uD83D').append('\uDE00'); + System.out.println(sb.length()); + System.out.println(sb.toString()); + + StringBuffer lone = new StringBuffer(); + lone.append('\uD800'); + System.out.println(lone.length()); + + System.out.println(String.valueOf('\uD800').length()); + System.out.println('\uD800'); + + char[] arr = { '\uD83D', '\uDE00' }; + StringBuffer sb2 = new StringBuffer(); + sb2.append(arr, 0, 2); + System.out.println(sb2.length()); + } +} diff --git a/test_data/src/ZipCorrupt.java b/test_data/src/ZipCorrupt.java new file mode 100644 index 00000000..e84a23ad --- /dev/null +++ b/test_data/src/ZipCorrupt.java @@ -0,0 +1,14 @@ +import java.io.File; +import java.util.zip.ZipException; +import java.util.zip.ZipFile; + +public class ZipCorrupt { + public static void main(String[] args) throws Exception { + try { + new ZipFile(new File("test_data/FileLength.txt")); + System.out.println("no-ex"); + } catch (ZipException e) { + System.out.println("zipex"); + } + } +}