diff --git a/rustjava-runtime/src/classes/java/io/buffered_output_stream.rs b/rustjava-runtime/src/classes/java/io/buffered_output_stream.rs index fc48d5b0..13bdfee1 100644 --- a/rustjava-runtime/src/classes/java/io/buffered_output_stream.rs +++ b/rustjava-runtime/src/classes/java/io/buffered_output_stream.rs @@ -120,6 +120,9 @@ impl BufferedOutputStream { length: i32, ) -> Result<()> { tracing::debug!("java.io.BufferedOutputStream::write({this:?}, {bytes:?}, {offset}, {length})"); + if bytes.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "argument is null").await); + } let source_length = jvm.array_length(&bytes).await? as i32; if offset < 0 || length < 0 || offset > source_length - length { diff --git a/rustjava-runtime/src/classes/java/io/data_input_stream.rs b/rustjava-runtime/src/classes/java/io/data_input_stream.rs index ea029a08..7c350247 100644 --- a/rustjava-runtime/src/classes/java/io/data_input_stream.rs +++ b/rustjava-runtime/src/classes/java/io/data_input_stream.rs @@ -214,12 +214,18 @@ impl DataInputStream { async fn read_utf_from_input(jvm: &Jvm, _: &mut RuntimeContext, input: ClassInstanceRef) -> Result> { tracing::debug!("java.io.DataInputStream::readUTF({input:?})"); + if input.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "argument is null").await); + } jvm.invoke_virtual(&input, &input.class_definition().name(), "readUTF", "()Ljava/lang/String;", ()) .await } async fn read_fully(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, b: ClassInstanceRef>) -> Result<()> { tracing::debug!("java.io.DataInputStream::readFully({this:?}, {b:?})"); + if b.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "argument is null").await); + } let length = jvm.array_length(&b).await?; @@ -239,6 +245,9 @@ impl DataInputStream { len: i32, ) -> Result<()> { tracing::debug!("java.io.DataInputStream::readFully({this:?}, {b:?}, {off}, {len})"); + if b.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "argument is null").await); + } let mut read = 0; while read < len { @@ -257,14 +266,23 @@ impl DataInputStream { async fn skip_bytes(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, n: i32) -> Result { tracing::debug!("java.io.DataInputStream::skipBytes({this:?}, {n:?})"); - let r#in = jvm.get_field(&this, "java/io/DataInputStream", "in", "Ljava/io/InputStream;").await?; + if n <= 0 { + return Ok(0); + } + let r#in: ClassInstanceRef = jvm.get_field(&this, "java/io/DataInputStream", "in", "Ljava/io/InputStream;").await?; + if r#in.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "underlying stream is null").await); + } let skipped: i64 = jvm.invoke_virtual(&r#in, "java/io/InputStream", "skip", "(J)J", (n as i64,)).await?; Ok(skipped as _) } async fn read_required_byte(jvm: &Jvm, this: &ClassInstanceRef) -> Result { - let r#in = jvm.get_field(this, "java/io/DataInputStream", "in", "Ljava/io/InputStream;").await?; + let r#in: ClassInstanceRef = jvm.get_field(this, "java/io/DataInputStream", "in", "Ljava/io/InputStream;").await?; + if r#in.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "underlying stream is null").await); + } let value: i32 = jvm.invoke_virtual(&r#in, "java/io/InputStream", "read", "()I", ()).await?; if value == -1 { return Err(jvm.exception("java/io/EOFException", "End of stream").await); diff --git a/rustjava-runtime/src/classes/java/io/data_output_stream.rs b/rustjava-runtime/src/classes/java/io/data_output_stream.rs index cdbc1989..1c24289d 100644 --- a/rustjava-runtime/src/classes/java/io/data_output_stream.rs +++ b/rustjava-runtime/src/classes/java/io/data_output_stream.rs @@ -105,7 +105,10 @@ impl DataOutputStream { async fn write(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, b: i32) -> Result<()> { tracing::debug!("java.io.DataOutputStream::write({this:?}, {b:?})"); - let out = jvm.get_field(&this, "java/io/DataOutputStream", "out", "Ljava/io/OutputStream;").await?; + let out: ClassInstanceRef = jvm.get_field(&this, "java/io/DataOutputStream", "out", "Ljava/io/OutputStream;").await?; + if out.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "underlying stream is null").await); + } let _: () = jvm.invoke_virtual(&out, "java/io/OutputStream", "write", "(I)V", (b,)).await?; Ok(()) @@ -114,7 +117,10 @@ impl DataOutputStream { async fn write_byte(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, v: i32) -> Result<()> { tracing::debug!("java.io.DataOutputStream::writeByte({this:?}, {v:?})"); - let out = jvm.get_field(&this, "java/io/DataOutputStream", "out", "Ljava/io/OutputStream;").await?; + let out: ClassInstanceRef = jvm.get_field(&this, "java/io/DataOutputStream", "out", "Ljava/io/OutputStream;").await?; + if out.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "underlying stream is null").await); + } let _: () = jvm.invoke_virtual(&out, "java/io/OutputStream", "write", "(I)V", (v,)).await?; Ok(()) @@ -137,7 +143,10 @@ impl DataOutputStream { let mut byte_array = jvm.instantiate_array("B", bytes.len() as _).await?; jvm.store_array(&mut byte_array, 0, cast_vec::(bytes.to_vec())).await?; - let out = jvm.get_field(&this, "java/io/DataOutputStream", "out", "Ljava/io/OutputStream;").await?; + let out: ClassInstanceRef = jvm.get_field(&this, "java/io/DataOutputStream", "out", "Ljava/io/OutputStream;").await?; + if out.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "underlying stream is null").await); + } let _: () = jvm.invoke_virtual(&out, "java/io/OutputStream", "write", "([B)V", (byte_array,)).await?; Ok(()) @@ -156,7 +165,10 @@ impl DataOutputStream { let mut byte_array = jvm.instantiate_array("B", bytes.len() as _).await?; jvm.store_array(&mut byte_array, 0, cast_vec::(bytes.to_vec())).await?; - let out = jvm.get_field(&this, "java/io/DataOutputStream", "out", "Ljava/io/OutputStream;").await?; + let out: ClassInstanceRef = jvm.get_field(&this, "java/io/DataOutputStream", "out", "Ljava/io/OutputStream;").await?; + if out.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "underlying stream is null").await); + } let _: () = jvm.invoke_virtual(&out, "java/io/OutputStream", "write", "([B)V", (byte_array,)).await?; Ok(()) @@ -169,7 +181,10 @@ impl DataOutputStream { let mut byte_array = jvm.instantiate_array("B", bytes.len() as _).await?; jvm.store_array(&mut byte_array, 0, cast_vec::(bytes.to_vec())).await?; - let out = jvm.get_field(&this, "java/io/DataOutputStream", "out", "Ljava/io/OutputStream;").await?; + let out: ClassInstanceRef = jvm.get_field(&this, "java/io/DataOutputStream", "out", "Ljava/io/OutputStream;").await?; + if out.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "underlying stream is null").await); + } let _: () = jvm.invoke_virtual(&out, "java/io/OutputStream", "write", "([B)V", (byte_array,)).await?; Ok(()) @@ -189,23 +204,38 @@ impl DataOutputStream { async fn write_bytes(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, s: ClassInstanceRef) -> Result<()> { tracing::debug!("java.io.DataOutputStream::writeBytes({this:?}, {s:?})"); + if s.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "argument is null").await); + } let chars: ClassInstanceRef> = jvm.invoke_virtual(&s, "java/lang/String", "toCharArray", "()[C", ()).await?; let length = jvm.array_length(&chars).await?; let chars: Vec = jvm.load_array(&chars, 0, length).await?; + if chars.is_empty() { + return Ok(()); + } let mut bytes = jvm.instantiate_array("B", chars.len()).await?; jvm.store_array(&mut bytes, 0, chars.into_iter().map(|value| value as i8)).await?; - let out = jvm.get_field(&this, "java/io/DataOutputStream", "out", "Ljava/io/OutputStream;").await?; + let out: ClassInstanceRef = jvm.get_field(&this, "java/io/DataOutputStream", "out", "Ljava/io/OutputStream;").await?; + if out.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "underlying stream is null").await); + } jvm.invoke_virtual(&out, "java/io/OutputStream", "write", "([B)V", (bytes,)).await } async fn write_chars(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, s: ClassInstanceRef) -> Result<()> { tracing::debug!("java.io.DataOutputStream::writeChars({this:?}, {s:?})"); + if s.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "argument is null").await); + } let chars: ClassInstanceRef> = jvm.invoke_virtual(&s, "java/lang/String", "toCharArray", "()[C", ()).await?; let length = jvm.array_length(&chars).await?; let chars: Vec = jvm.load_array(&chars, 0, length).await?; + if chars.is_empty() { + return Ok(()); + } let mut data = Vec::with_capacity(chars.len() * 2); for value in chars { data.push((value >> 8) as i8); @@ -215,12 +245,18 @@ impl DataOutputStream { let mut bytes = jvm.instantiate_array("B", data.len()).await?; jvm.store_array(&mut bytes, 0, data).await?; - let out = jvm.get_field(&this, "java/io/DataOutputStream", "out", "Ljava/io/OutputStream;").await?; + let out: ClassInstanceRef = jvm.get_field(&this, "java/io/DataOutputStream", "out", "Ljava/io/OutputStream;").await?; + if out.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "underlying stream is null").await); + } jvm.invoke_virtual(&out, "java/io/OutputStream", "write", "([B)V", (bytes,)).await } async fn write_utf(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, s: ClassInstanceRef) -> Result<()> { tracing::debug!("java.io.DataOutputStream::writeUTF({this:?}, {s:?})"); + if s.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "argument is null").await); + } let chars: ClassInstanceRef> = jvm.invoke_virtual(&s, "java/lang/String", "toCharArray", "()[C", ()).await?; let length = jvm.array_length(&chars).await?; @@ -238,14 +274,20 @@ impl DataOutputStream { let mut bytes = jvm.instantiate_array("B", data.len()).await?; jvm.store_array(&mut bytes, 0, data).await?; - let out = jvm.get_field(&this, "java/io/DataOutputStream", "out", "Ljava/io/OutputStream;").await?; + let out: ClassInstanceRef = jvm.get_field(&this, "java/io/DataOutputStream", "out", "Ljava/io/OutputStream;").await?; + if out.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "underlying stream is null").await); + } jvm.invoke_virtual(&out, "java/io/OutputStream", "write", "([B)V", (bytes,)).await } async fn close(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result<()> { tracing::debug!("java.io.DataOutputStream::close({this:?})"); - let out = jvm.get_field(&this, "java/io/DataOutputStream", "out", "Ljava/io/OutputStream;").await?; + let out: ClassInstanceRef = jvm.get_field(&this, "java/io/DataOutputStream", "out", "Ljava/io/OutputStream;").await?; + if out.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "underlying stream is null").await); + } let _: () = jvm.invoke_virtual(&out, "java/io/OutputStream", "close", "()V", []).await?; Ok(()) @@ -254,7 +296,10 @@ impl DataOutputStream { async fn flush(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result<()> { tracing::debug!("java.io.DataOutputStream::flush({this:?})"); - let out = jvm.get_field(&this, "java/io/DataOutputStream", "out", "Ljava/io/OutputStream;").await?; + let out: ClassInstanceRef = jvm.get_field(&this, "java/io/DataOutputStream", "out", "Ljava/io/OutputStream;").await?; + if out.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "underlying stream is null").await); + } let _: () = jvm.invoke_virtual(&out, "java/io/OutputStream", "flush", "()V", []).await?; Ok(()) diff --git a/rustjava-runtime/src/classes/java/io/file.rs b/rustjava-runtime/src/classes/java/io/file.rs index 654d5d4d..10bcc8d7 100644 --- a/rustjava-runtime/src/classes/java/io/file.rs +++ b/rustjava-runtime/src/classes/java/io/file.rs @@ -84,6 +84,9 @@ impl File { async fn init(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, pathname: ClassInstanceRef) -> Result<()> { tracing::debug!("java.io.File::({this:?}, {pathname:?})"); + if pathname.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "argument is null").await); + } let _: () = jvm.invoke_special(&this, "java/lang/Object", "", "()V", ()).await?; diff --git a/rustjava-runtime/src/classes/java/io/file_descriptor.rs b/rustjava-runtime/src/classes/java/io/file_descriptor.rs index fab44f0a..a296623c 100644 --- a/rustjava-runtime/src/classes/java/io/file_descriptor.rs +++ b/rustjava-runtime/src/classes/java/io/file_descriptor.rs @@ -32,33 +32,13 @@ impl FileDescriptor { async fn cl_init(jvm: &Jvm, runtime: &mut RuntimeContext) -> Result<()> { tracing::debug!("java.io.FileDescriptor::()"); - let stderr_fd = runtime.stderr(); - if let Ok(stderr_fd) = stderr_fd { - let mut stderr = jvm.new_class("java/io/FileDescriptor", "()V", []).await?; - jvm.put_field(&mut stderr, "java/io/FileDescriptor", "fd", "I", stderr_fd.id() as i32) - .await?; - - jvm.put_static_field("java/io/FileDescriptor", "err", "Ljava/io/FileDescriptor;", stderr) - .await?; - } - - let stdin_fd = runtime.stdin(); - if let Ok(stdin_fd) = stdin_fd { - let mut stdin = jvm.new_class("java/io/FileDescriptor", "()V", []).await?; - jvm.put_field(&mut stdin, "java/io/FileDescriptor", "fd", "I", stdin_fd.id() as i32) - .await?; - - jvm.put_static_field("java/io/FileDescriptor", "in", "Ljava/io/FileDescriptor;", stdin) - .await?; - } - - let stdout_fd = runtime.stdout(); - if let Ok(stdout_fd) = stdout_fd { - let mut stdout = jvm.new_class("java/io/FileDescriptor", "()V", []).await?; - jvm.put_field(&mut stdout, "java/io/FileDescriptor", "fd", "I", stdout_fd.id() as i32) - .await?; - - jvm.put_static_field("java/io/FileDescriptor", "out", "Ljava/io/FileDescriptor;", stdout) + for (name, host_descriptor) in [("err", runtime.stderr()), ("in", runtime.stdin()), ("out", runtime.stdout())] { + let mut descriptor = jvm.new_class("java/io/FileDescriptor", "()V", []).await?; + if let Ok(host_descriptor) = host_descriptor { + jvm.put_field(&mut descriptor, "java/io/FileDescriptor", "fd", "I", host_descriptor.id() as i32) + .await?; + } + jvm.put_static_field("java/io/FileDescriptor", name, "Ljava/io/FileDescriptor;", descriptor) .await?; } diff --git a/rustjava-runtime/src/classes/java/io/file_input_stream.rs b/rustjava-runtime/src/classes/java/io/file_input_stream.rs index b6ed3a3c..751b5f24 100644 --- a/rustjava-runtime/src/classes/java/io/file_input_stream.rs +++ b/rustjava-runtime/src/classes/java/io/file_input_stream.rs @@ -44,6 +44,9 @@ impl FileInputStream { async fn init(jvm: &Jvm, context: &mut RuntimeContext, this: ClassInstanceRef, file: ClassInstanceRef) -> Result<()> { tracing::debug!("java.io.FileInputStream::({this:?}, {file:?})"); + if file.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "argument is null").await); + } let path = jvm.invoke_virtual(&file, "java/io/File", "getPath", "()Ljava/lang/String;", ()).await?; let path = JavaLangString::to_rust_string(jvm, &path).await?; @@ -77,6 +80,9 @@ impl FileInputStream { fd: ClassInstanceRef, ) -> Result<()> { tracing::debug!("java.io.FileInputStream::({this:?}, {fd:?})"); + if fd.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "argument is null").await); + } let _: () = jvm.invoke_special(&this, "java/io/InputStream", "", "()V", ()).await?; @@ -114,6 +120,9 @@ impl FileInputStream { length: i32, ) -> Result { tracing::debug!("java.io.FileInputStream::read({this:?}, {buf:?}, {offset:?}, {length:?})"); + if buf.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "argument is null").await); + } let fd = jvm.get_field(&this, "java/io/FileInputStream", "fd", "Ljava/io/FileDescriptor;").await?; let mut rust_file = FileDescriptor::file(jvm, context, fd).await?; diff --git a/rustjava-runtime/src/classes/java/io/file_output_stream.rs b/rustjava-runtime/src/classes/java/io/file_output_stream.rs index 3c15fa00..fd5b2b42 100644 --- a/rustjava-runtime/src/classes/java/io/file_output_stream.rs +++ b/rustjava-runtime/src/classes/java/io/file_output_stream.rs @@ -55,6 +55,9 @@ impl FileOutputStream { append: bool, ) -> Result<()> { tracing::debug!("java.io.FileOutputStream::({this:?}, {file:?}, {append})"); + if file.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "argument is null").await); + } let path = jvm.invoke_virtual(&file, "java/io/File", "getPath", "()Ljava/lang/String;", ()).await?; let path = JavaLangString::to_rust_string(jvm, &path).await?; @@ -89,6 +92,9 @@ impl FileOutputStream { file_descriptor: ClassInstanceRef, ) -> Result<()> { tracing::debug!("java.io.FileOutputStream::({this:?}, {file_descriptor:?})"); + if file_descriptor.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "argument is null").await); + } let _: () = jvm.invoke_special(&this, "java/io/OutputStream", "", "()V", ()).await?; @@ -108,6 +114,9 @@ impl FileOutputStream { length: i32, ) -> Result<()> { tracing::debug!("java.io.FileOutputStream::write({this:?}, {buffer:?}, {offset:?}, {length:?})"); + if buffer.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "argument is null").await); + } let fd = jvm.get_field(&this, "java/io/FileOutputStream", "fd", "Ljava/io/FileDescriptor;").await?; let mut file = FileDescriptor::file(jvm, context, fd).await?; diff --git a/rustjava-runtime/src/classes/java/io/filter_input_stream.rs b/rustjava-runtime/src/classes/java/io/filter_input_stream.rs index da4c58e3..3476d12e 100644 --- a/rustjava-runtime/src/classes/java/io/filter_input_stream.rs +++ b/rustjava-runtime/src/classes/java/io/filter_input_stream.rs @@ -46,7 +46,10 @@ impl FilterInputStream { async fn available(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { tracing::debug!("java.io.FilterInputStream::available({this:?})"); - let r#in = jvm.get_field(&this, "java/io/FilterInputStream", "in", "Ljava/io/InputStream;").await?; + let r#in: ClassInstanceRef = jvm.get_field(&this, "java/io/FilterInputStream", "in", "Ljava/io/InputStream;").await?; + if r#in.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "underlying stream is null").await); + } let available: i32 = jvm.invoke_virtual(&r#in, "java/io/InputStream", "available", "()I", ()).await?; Ok(available) @@ -55,7 +58,10 @@ impl FilterInputStream { async fn close(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result<()> { tracing::debug!("java.io.FilterInputStream::close({this:?})"); - let r#in = jvm.get_field(&this, "java/io/FilterInputStream", "in", "Ljava/io/InputStream;").await?; + let r#in: ClassInstanceRef = jvm.get_field(&this, "java/io/FilterInputStream", "in", "Ljava/io/InputStream;").await?; + if r#in.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "underlying stream is null").await); + } let _: () = jvm.invoke_virtual(&r#in, "java/io/InputStream", "close", "()V", ()).await?; Ok(()) @@ -64,7 +70,10 @@ impl FilterInputStream { async fn reset(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result<()> { tracing::debug!("java.io.FilterInputStream::reset({this:?})"); - let r#in = jvm.get_field(&this, "java/io/FilterInputStream", "in", "Ljava/io/InputStream;").await?; + let r#in: ClassInstanceRef = jvm.get_field(&this, "java/io/FilterInputStream", "in", "Ljava/io/InputStream;").await?; + if r#in.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "underlying stream is null").await); + } let _: () = jvm.invoke_virtual(&r#in, "java/io/InputStream", "reset", "()V", ()).await?; Ok(()) @@ -72,26 +81,38 @@ impl FilterInputStream { async fn skip(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, n: i64) -> Result { tracing::debug!("java.io.FilterInputStream::skip({this:?}, {n})"); - let r#in = jvm.get_field(&this, "java/io/FilterInputStream", "in", "Ljava/io/InputStream;").await?; + let r#in: ClassInstanceRef = jvm.get_field(&this, "java/io/FilterInputStream", "in", "Ljava/io/InputStream;").await?; + if r#in.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "underlying stream is null").await); + } jvm.invoke_virtual(&r#in, "java/io/InputStream", "skip", "(J)J", (n,)).await } async fn mark(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, readlimit: i32) -> Result<()> { tracing::debug!("java.io.FilterInputStream::mark({this:?}, {readlimit})"); - let r#in = jvm.get_field(&this, "java/io/FilterInputStream", "in", "Ljava/io/InputStream;").await?; + let r#in: ClassInstanceRef = jvm.get_field(&this, "java/io/FilterInputStream", "in", "Ljava/io/InputStream;").await?; + if r#in.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "underlying stream is null").await); + } jvm.invoke_virtual(&r#in, "java/io/InputStream", "mark", "(I)V", (readlimit,)).await } async fn mark_supported(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { tracing::debug!("java.io.FilterInputStream::markSupported({this:?})"); - let r#in = jvm.get_field(&this, "java/io/FilterInputStream", "in", "Ljava/io/InputStream;").await?; + let r#in: ClassInstanceRef = jvm.get_field(&this, "java/io/FilterInputStream", "in", "Ljava/io/InputStream;").await?; + if r#in.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "underlying stream is null").await); + } jvm.invoke_virtual(&r#in, "java/io/InputStream", "markSupported", "()Z", ()).await } async fn read(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, b: ClassInstanceRef>) -> Result { tracing::debug!("java.io.FilterInputStream::read({this:?}, {b:?})"); - let r#in = jvm.get_field(&this, "java/io/FilterInputStream", "in", "Ljava/io/InputStream;").await?; + let r#in: ClassInstanceRef = jvm.get_field(&this, "java/io/FilterInputStream", "in", "Ljava/io/InputStream;").await?; + if r#in.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "underlying stream is null").await); + } let result: i32 = jvm.invoke_virtual(&r#in, "java/io/InputStream", "read", "([B)I", (b,)).await?; Ok(result) @@ -107,7 +128,10 @@ impl FilterInputStream { ) -> Result { tracing::debug!("java.io.FilterInputStream::read({this:?}, {b:?}, {off}, {len})"); - let r#in = jvm.get_field(&this, "java/io/FilterInputStream", "in", "Ljava/io/InputStream;").await?; + let r#in: ClassInstanceRef = jvm.get_field(&this, "java/io/FilterInputStream", "in", "Ljava/io/InputStream;").await?; + if r#in.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "underlying stream is null").await); + } let result: i32 = jvm.invoke_virtual(&r#in, "java/io/InputStream", "read", "([BII)I", (b, off, len)).await?; Ok(result) @@ -116,7 +140,10 @@ impl FilterInputStream { async fn read_byte_int(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { tracing::debug!("java.io.FilterInputStream::read({this:?})"); - let r#in = jvm.get_field(&this, "java/io/FilterInputStream", "in", "Ljava/io/InputStream;").await?; + let r#in: ClassInstanceRef = jvm.get_field(&this, "java/io/FilterInputStream", "in", "Ljava/io/InputStream;").await?; + if r#in.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "underlying stream is null").await); + } let result: i32 = jvm.invoke_virtual(&r#in, "java/io/InputStream", "read", "()I", ()).await?; Ok(result) diff --git a/rustjava-runtime/src/classes/java/io/filter_output_stream.rs b/rustjava-runtime/src/classes/java/io/filter_output_stream.rs index 09259da2..26696514 100644 --- a/rustjava-runtime/src/classes/java/io/filter_output_stream.rs +++ b/rustjava-runtime/src/classes/java/io/filter_output_stream.rs @@ -48,9 +48,22 @@ impl FilterOutputStream { ) -> Result<()> { tracing::debug!(" java.io.FilterOutputStream::write({this:?}, {bytes:?}, {offset:?}, {length:?})"); - let out = jvm + if bytes.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "buffer is null").await); + } + let array_length = jvm.array_length(&bytes).await? as i32; + if offset < 0 || length < 0 || offset > array_length - length { + return Err(jvm.exception("java/lang/IndexOutOfBoundsException", "Invalid offset or length").await); + } + if length == 0 { + return Ok(()); + } + let out: ClassInstanceRef = jvm .get_field(&this, "java/io/FilterOutputStream", "out", "Ljava/io/OutputStream;") .await?; + if out.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "underlying stream is null").await); + } let _: () = jvm .invoke_virtual(&out, "java/io/OutputStream", "write", "([BII)V", (bytes, offset, length)) .await?; @@ -61,9 +74,12 @@ impl FilterOutputStream { async fn write(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, byte: i32) -> Result<()> { tracing::debug!("java.io.FilterOutputStream::write({this:?}, {byte:?})"); - let out = jvm + let out: ClassInstanceRef = jvm .get_field(&this, "java/io/FilterOutputStream", "out", "Ljava/io/OutputStream;") .await?; + if out.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "underlying stream is null").await); + } let _: () = jvm.invoke_virtual(&out, "java/io/OutputStream", "write", "(I)V", (byte,)).await?; Ok(()) @@ -71,9 +87,12 @@ impl FilterOutputStream { async fn flush(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result<()> { tracing::debug!("java.io.FilterOutputStream::flush({this:?})"); - let out = jvm + let out: ClassInstanceRef = jvm .get_field(&this, "java/io/FilterOutputStream", "out", "Ljava/io/OutputStream;") .await?; + if out.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "underlying stream is null").await); + } jvm.invoke_virtual(&out, "java/io/OutputStream", "flush", "()V", ()).await } diff --git a/rustjava-runtime/src/classes/java/io/input_stream_reader.rs b/rustjava-runtime/src/classes/java/io/input_stream_reader.rs index 129339a2..663d866a 100644 --- a/rustjava-runtime/src/classes/java/io/input_stream_reader.rs +++ b/rustjava-runtime/src/classes/java/io/input_stream_reader.rs @@ -114,6 +114,9 @@ impl InputStreamReader { length: i32, ) -> Result { tracing::debug!("java.io.InputStreamReader::read({this:?}, {buf:?}, {offset:?}, {length:?})"); + if buf.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "argument is null").await); + } let destination_length = jvm.array_length(&buf).await? as i32; if offset < 0 || length < 0 || offset > destination_length - length { diff --git a/rustjava-runtime/src/classes/java/io/output_stream_writer.rs b/rustjava-runtime/src/classes/java/io/output_stream_writer.rs index 2b82dd01..e3367261 100644 --- a/rustjava-runtime/src/classes/java/io/output_stream_writer.rs +++ b/rustjava-runtime/src/classes/java/io/output_stream_writer.rs @@ -106,6 +106,9 @@ impl OutputStreamWriter { len: i32, ) -> Result<()> { tracing::debug!("java.io.OutputStreamWriter::write({this:?}, {chars:?}, {off}, {len})"); + if chars.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "argument is null").await); + } let array_length = jvm.array_length(&chars).await? as i32; if off < 0 || len < 0 || off > array_length - len { diff --git a/rustjava-runtime/src/classes/java/io/random_access_file.rs b/rustjava-runtime/src/classes/java/io/random_access_file.rs index 56927744..9b18916e 100644 --- a/rustjava-runtime/src/classes/java/io/random_access_file.rs +++ b/rustjava-runtime/src/classes/java/io/random_access_file.rs @@ -64,6 +64,9 @@ impl RandomAccessFile { mode: ClassInstanceRef, ) -> Result<()> { tracing::debug!("java.io.RandomAccessFile::({this:?}, {name:?}, {mode:?})"); + if name.is_null() || mode.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "argument is null").await); + } let _: () = jvm.invoke_special(&this, "java/lang/Object", "", "()V", ()).await?; @@ -101,6 +104,9 @@ impl RandomAccessFile { mode: ClassInstanceRef, ) -> Result<()> { tracing::debug!("java.io.RandomAccessFile::({this:?}, {file:?}, {mode:?})"); + if file.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "argument is null").await); + } let name: ClassInstanceRef = jvm.invoke_virtual(&file, "java/io/File", "getPath", "()Ljava/lang/String;", ()).await?; @@ -119,6 +125,9 @@ impl RandomAccessFile { async fn read(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, buf: ClassInstanceRef>) -> Result { tracing::debug!("java.io.RandomAccessFile::read({this:?}, {buf:?})"); + if buf.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "argument is null").await); + } let length = jvm.array_length(&buf).await?; let read = jvm @@ -137,6 +146,9 @@ impl RandomAccessFile { length: i32, ) -> Result { tracing::debug!("java.io.RandomAccessFile::read({this:?}, {buf:?}, {offset:?}, {length:?})"); + if buf.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "argument is null").await); + } let fd = jvm.get_field(&this, "java/io/RandomAccessFile", "fd", "Ljava/io/FileDescriptor;").await?; let mut rust_file = FileDescriptor::file(jvm, context, fd).await?; @@ -153,6 +165,9 @@ impl RandomAccessFile { async fn write(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, buf: ClassInstanceRef>) -> Result<()> { tracing::debug!("java.io.RandomAccessFile::write({this:?}, {buf:?})"); + if buf.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "argument is null").await); + } let length = jvm.array_length(&buf).await?; let _: () = jvm @@ -171,6 +186,9 @@ impl RandomAccessFile { length: i32, ) -> Result<()> { tracing::debug!("java.io.RandomAccessFile::write({this:?}, {buf:?}, {offset:?}, {length:?})"); + if buf.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "argument is null").await); + } let fd = jvm.get_field(&this, "java/io/RandomAccessFile", "fd", "Ljava/io/FileDescriptor;").await?; let mut rust_file = FileDescriptor::file(jvm, context, fd).await?; diff --git a/rustjava-runtime/src/classes/java/io/reader.rs b/rustjava-runtime/src/classes/java/io/reader.rs index 5c87c030..dd1a3796 100644 --- a/rustjava-runtime/src/classes/java/io/reader.rs +++ b/rustjava-runtime/src/classes/java/io/reader.rs @@ -73,6 +73,9 @@ impl Reader { async fn read(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, buf: ClassInstanceRef>) -> Result { tracing::debug!("java.io.Reader::read({this:?}, {buf:?})"); + if buf.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "argument is null").await); + } let len = jvm.array_length(&buf).await? as i32; let result = jvm.invoke_virtual(&this, "java/io/Reader", "read", "([CII)I", (buf, 0, len)).await?; diff --git a/rustjava-runtime/src/classes/java/io/writer.rs b/rustjava-runtime/src/classes/java/io/writer.rs index 4639183f..a6103466 100644 --- a/rustjava-runtime/src/classes/java/io/writer.rs +++ b/rustjava-runtime/src/classes/java/io/writer.rs @@ -98,6 +98,9 @@ impl Writer { async fn write_chars(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, chars: ClassInstanceRef>) -> Result<()> { tracing::debug!("java.io.Writer::write({this:?}, {chars:?})"); + if chars.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "argument is null").await); + } let length = jvm.array_length(&chars).await? as i32; jvm.invoke_virtual(&this, "java/io/Writer", "write", "([CII)V", (chars, 0, length)).await @@ -105,6 +108,9 @@ impl Writer { async fn write_string(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, string: ClassInstanceRef) -> Result<()> { tracing::debug!("java.io.Writer::write_string({this:?}, {string:?})"); + if string.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "argument is null").await); + } let chars: ClassInstanceRef> = jvm.invoke_virtual(&string, "java/lang/String", "toCharArray", "()[C", ()).await?; let length = jvm.array_length(&chars).await?; @@ -125,6 +131,9 @@ impl Writer { len: i32, ) -> Result<()> { tracing::debug!("java.io.Writer::write({this:?}, {string:?}, {off}, {len})"); + if string.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "argument is null").await); + } let chars: ClassInstanceRef> = jvm.invoke_virtual(&string, "java/lang/String", "toCharArray", "()[C", ()).await?; jvm.invoke_virtual(&this, "java/io/Writer", "write", "([CII)V", (chars, off, len)).await diff --git a/rustjava-runtime/src/classes/java/lang/class_loader.rs b/rustjava-runtime/src/classes/java/lang/class_loader.rs index 812d0c48..bd37dd3c 100644 --- a/rustjava-runtime/src/classes/java/lang/class_loader.rs +++ b/rustjava-runtime/src/classes/java/lang/class_loader.rs @@ -192,6 +192,10 @@ impl ClassLoader { ) -> Result> { tracing::debug!("java.lang.ClassLoader::loadClass({this:?}, {name:?})"); + if name.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "name is null").await); + } + let class: ClassInstanceRef = jvm .invoke_virtual( &this, @@ -284,6 +288,10 @@ impl ClassLoader { ) -> Result> { tracing::debug!("java.lang.ClassLoader::findLoadedClass({this:?}, {name:?})"); + if name.is_null() { + return Ok(None.into()); + } + let internal_name = JavaLangString::to_rust_string(jvm, &name).await?.replace('.', "/"); if !jvm.has_class(&internal_name) { return Ok(None.into()); @@ -302,6 +310,10 @@ impl ClassLoader { ) -> Result> { tracing::debug!("java.lang.ClassLoader::getResource({this:?})"); + if name.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "name is null").await); + } + let parent: ClassInstanceRef = jvm.get_field(&this, "java/lang/ClassLoader", "parent", "Ljava/lang/ClassLoader;").await?; let result: ClassInstanceRef = if !parent.is_null() { diff --git a/rustjava-runtime/src/classes/java/lang/integer.rs b/rustjava-runtime/src/classes/java/lang/integer.rs index 8302e63a..e778159e 100644 --- a/rustjava-runtime/src/classes/java/lang/integer.rs +++ b/rustjava-runtime/src/classes/java/lang/integer.rs @@ -254,7 +254,7 @@ impl Integer { async fn decode(jvm: &Jvm, _: &mut RuntimeContext, value: ClassInstanceRef) -> Result> { if value.is_null() { - return Err(jvm.exception("java/lang/NumberFormatException", "null").await); + return Err(jvm.exception("java/lang/NullPointerException", "value").await); } let value = JavaLangString::to_rust_string(jvm, &value).await?; let (sign, body) = if let Some(body) = value.strip_prefix('-') { diff --git a/rustjava-runtime/src/classes/java/lang/long.rs b/rustjava-runtime/src/classes/java/lang/long.rs index de6112cb..8d7e5de7 100644 --- a/rustjava-runtime/src/classes/java/lang/long.rs +++ b/rustjava-runtime/src/classes/java/lang/long.rs @@ -235,7 +235,7 @@ impl Long { } async fn decode(jvm: &Jvm, _: &mut RuntimeContext, value: ClassInstanceRef) -> Result> { if value.is_null() { - return Err(jvm.exception("java/lang/NumberFormatException", "null").await); + return Err(jvm.exception("java/lang/NullPointerException", "value").await); } let value = JavaLangString::to_rust_string(jvm, &value).await?; let (sign, body) = if let Some(body) = value.strip_prefix('-') { diff --git a/rustjava-runtime/src/classes/java/lang/string.rs b/rustjava-runtime/src/classes/java/lang/string.rs index 6cfd1355..251988b5 100644 --- a/rustjava-runtime/src/classes/java/lang/string.rs +++ b/rustjava-runtime/src/classes/java/lang/string.rs @@ -257,6 +257,10 @@ impl String { async fn init_with_byte_array(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, value: ClassInstanceRef>) -> Result<()> { tracing::debug!("java.lang.String::({this:?}, {value:?})"); + if value.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "value is null").await); + } + let count = jvm.array_length(&value).await? as i32; let _: () = jvm @@ -274,6 +278,10 @@ impl String { ) -> Result<()> { tracing::debug!("java.lang.String::({this:?}, {value:?})"); + if value.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "value is null").await); + } + let count = jvm.array_length(&value).await? as i32; let _: () = jvm @@ -363,6 +371,10 @@ impl String { ) -> Result<()> { tracing::debug!("java.lang.String::({this:?}, {value:?}, {offset}, {count})"); + if value.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "value is null").await); + } + let bytes: Vec = jvm.load_array(&value, offset as _, count as _).await?; let charset = System::get_charset(jvm).await?; @@ -384,6 +396,10 @@ impl String { async fn init_with_string(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, value: ClassInstanceRef) -> Result<()> { tracing::debug!("java.lang.String::({this:?}, {value:?})"); + if value.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "value is null").await); + } + let (original_value, offset, count) = Self::value_range(jvm, &value).await?; let length = jvm.array_length(&original_value).await?; @@ -413,6 +429,10 @@ impl String { ) -> Result<()> { tracing::debug!("java.lang.String::({this:?}, {value:?})"); + if value.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "value is null").await); + } + let string: ClassInstanceRef = jvm .invoke_virtual(&value, "java/lang/Object", "toString", "()Ljava/lang/String;", ()) .await?; @@ -547,6 +567,10 @@ impl String { ) -> Result> { tracing::debug!("java.lang.String::concat({this:?}, {other:?})"); + if other.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "other is null").await); + } + let this_string = JavaLangString::to_rust_string(jvm, &this.clone()).await?; let other_string = JavaLangString::to_rust_string(jvm, &other.clone()).await?; @@ -590,6 +614,9 @@ impl String { .await); } + if dst.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "dst is null").await); + } let chars: Vec = jvm .load_array(&value, offset + src_begin as usize, (src_end - src_begin) as usize) .await?; @@ -884,6 +911,10 @@ impl String { ) -> Result<()> { tracing::debug!("java.lang.String::({this:?}, {value:?}, {charset_name:?})"); + if value.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "value is null").await); + } + let count = jvm.array_length(&value).await? as i32; let _: () = jvm @@ -914,6 +945,10 @@ impl String { return Err(jvm.exception("java/lang/NullPointerException", "charsetName is null").await); } + if value.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "value is null").await); + } + let bytes: Vec = jvm.load_array(&value, offset as _, count as _).await?; let charset = JavaLangString::to_rust_string(jvm, &charset_name).await?; diff --git a/rustjava-runtime/src/classes/java/lang/system.rs b/rustjava-runtime/src/classes/java/lang/system.rs index 154669df..2b92ff79 100644 --- a/rustjava-runtime/src/classes/java/lang/system.rs +++ b/rustjava-runtime/src/classes/java/lang/system.rs @@ -116,12 +116,10 @@ impl System { let in_descriptor: ClassInstanceRef = jvm.get_static_field("java/io/FileDescriptor", "in", "Ljava/io/FileDescriptor;").await?; - if !in_descriptor.is_null() { - let input = jvm - .new_class("java/io/FileInputStream", "(Ljava/io/FileDescriptor;)V", (in_descriptor,)) - .await?; - jvm.put_static_field("java/lang/System", "in", "Ljava/io/InputStream;", input).await?; - } + let input = jvm + .new_class("java/io/FileInputStream", "(Ljava/io/FileDescriptor;)V", (in_descriptor,)) + .await?; + jvm.put_static_field("java/lang/System", "in", "Ljava/io/InputStream;", input).await?; let out_descriptor: ClassInstanceRef = jvm.get_static_field("java/io/FileDescriptor", "out", "Ljava/io/FileDescriptor;").await?; @@ -175,6 +173,10 @@ impl System { ) -> Result<()> { tracing::debug!("java.lang.System::arraycopy({src:?}, {src_pos}, {dest:?}, {dest_pos}, {length})"); + if src.is_null() || dest.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "array is null").await); + } + // TODO i think we can make it faster let src: Vec = jvm.load_array(&src, src_pos as _, length as _).await?; jvm.store_array(&mut dest, dest_pos as _, src).await?; diff --git a/rustjava-runtime/src/classes/java/lang/throwable.rs b/rustjava-runtime/src/classes/java/lang/throwable.rs index 4462bcfc..431a3195 100644 --- a/rustjava-runtime/src/classes/java/lang/throwable.rs +++ b/rustjava-runtime/src/classes/java/lang/throwable.rs @@ -221,6 +221,10 @@ impl Throwable { ) -> Result<()> { tracing::debug!("java.lang.Throwable::printStackTrace({this:?}, {stream:?})"); + if stream.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "stream is null").await); + } + Self::do_print_stack_trace(jvm, this, stream.into()).await?; Ok(()) @@ -234,6 +238,10 @@ impl Throwable { ) -> Result<()> { tracing::debug!("java.lang.Throwable::printStackTrace({this:?}, {writer:?})"); + if writer.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "writer is null").await); + } + Self::do_print_stack_trace(jvm, this, writer.into()).await?; Ok(()) diff --git a/rustjava-runtime/src/classes/java/net/jar_url_connection.rs b/rustjava-runtime/src/classes/java/net/jar_url_connection.rs index 0f1b3527..d72ed7ad 100644 --- a/rustjava-runtime/src/classes/java/net/jar_url_connection.rs +++ b/rustjava-runtime/src/classes/java/net/jar_url_connection.rs @@ -47,6 +47,10 @@ impl JarURLConnection { async fn init(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, url: ClassInstanceRef) -> Result<()> { tracing::debug!("java.net.JarURLConnection::({this:?}, {url:?})"); + if url.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "url is null").await); + } + let _: () = jvm .invoke_special(&this, "java/net/URLConnection", "", "(Ljava/net/URL;)V", (url.clone(),)) .await?; diff --git a/rustjava-runtime/src/classes/java/net/url.rs b/rustjava-runtime/src/classes/java/net/url.rs index 773b7c77..cf5632d4 100644 --- a/rustjava-runtime/src/classes/java/net/url.rs +++ b/rustjava-runtime/src/classes/java/net/url.rs @@ -124,6 +124,10 @@ impl URL { ) -> Result<()> { tracing::debug!("java.net.URL::({this:?}, {context:?}, {spec:?}, {handler:?})"); + if spec.is_null() { + return Err(jvm.exception("java/net/MalformedURLException", "spec is null").await); + } + let _: () = jvm.invoke_special(&this, "java/lang/Object", "", "()V", ()).await?; let spec_str = JavaLangString::to_rust_string(jvm, &spec).await?; @@ -184,6 +188,10 @@ impl URL { ) -> Result<()> { tracing::debug!("java.net.URL::({this:?}, {protocol:?}, {host:?}, {port:?}, {file:?}, {handler:?})"); + if protocol.is_null() || file.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "protocol or file is null").await); + } + let _: () = jvm.invoke_special(&this, "java/lang/Object", "", "()V", ()).await?; jvm.put_field(&mut this, "java/net/URL", "handler", "Ljava/net/URLStreamHandler;", handler.clone()) diff --git a/rustjava-runtime/src/classes/java/net/url_class_loader.rs b/rustjava-runtime/src/classes/java/net/url_class_loader.rs index ac4b7308..cedad2e4 100644 --- a/rustjava-runtime/src/classes/java/net/url_class_loader.rs +++ b/rustjava-runtime/src/classes/java/net/url_class_loader.rs @@ -59,6 +59,14 @@ impl URLClassLoader { ) -> Result<()> { tracing::debug!("java.net.URLClassLoader::({this:?}, {urls:?}, {parent:?})"); + if urls.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "urls is null").await); + } + let entries: Vec> = jvm.load_array(&urls, 0, jvm.array_length(&urls).await? as _).await?; + if entries.iter().any(|url| url.is_null()) { + return Err(jvm.exception("java/lang/NullPointerException", "URL element is null").await); + } + let _: () = jvm .invoke_special(&this, "java/lang/ClassLoader", "", "(Ljava/lang/ClassLoader;)V", (parent,)) .await?; @@ -77,6 +85,10 @@ impl URLClassLoader { ) -> Result> { tracing::debug!("java.net.URLClassLoader::findClass({this:?}, {name:?})"); + if name.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "name is null").await); + } + let name_str = JavaLangString::to_rust_string(jvm, &name).await?; let resource_name = format!("{}.class", name_str.replace('.', "/")); @@ -125,6 +137,10 @@ impl URLClassLoader { ) -> Result> { tracing::debug!("java.net.URLClassLoader::findResource({this:?}, {name:?})"); + if name.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "name is null").await); + } + let name_str = JavaLangString::to_rust_string(jvm, &name).await?; let urls = jvm.get_field(&this, "java/net/URLClassLoader", "urls", "[Ljava/net/URL;").await?; diff --git a/rustjava-runtime/src/classes/java/net/url_stream_handler.rs b/rustjava-runtime/src/classes/java/net/url_stream_handler.rs index 07a46979..265aeb7a 100644 --- a/rustjava-runtime/src/classes/java/net/url_stream_handler.rs +++ b/rustjava-runtime/src/classes/java/net/url_stream_handler.rs @@ -66,6 +66,10 @@ impl URLStreamHandler { &r#ref ); + if url.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "url is null").await); + } + let _: () = jvm .invoke_virtual( &url, @@ -90,6 +94,10 @@ impl URLStreamHandler { ) -> Result<()> { tracing::debug!("java.net.URLStreamHandler::parseURL({this:?}, {url:?}, {spec:?}, {start:?}, {limit:?})"); + if url.is_null() || spec.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "url or spec is null").await); + } + let spec_str = JavaLangString::to_rust_string(jvm, &spec).await?; let parsed_url = Url::parse(&spec_str); diff --git a/rustjava-runtime/src/classes/java/text/date_format.rs b/rustjava-runtime/src/classes/java/text/date_format.rs index c1dbdf01..d8a45af1 100644 --- a/rustjava-runtime/src/classes/java/text/date_format.rs +++ b/rustjava-runtime/src/classes/java/text/date_format.rs @@ -531,9 +531,6 @@ impl DateFormat { } async fn set_calendar(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, calendar: ClassInstanceRef) -> Result<()> { - if calendar.is_null() { - return Err(jvm.exception("java/lang/NullPointerException", "calendar").await); - } jvm.put_field(&mut this, "java/text/DateFormat", "calendar", "Ljava/util/Calendar;", calendar) .await } @@ -549,9 +546,6 @@ impl DateFormat { mut this: ClassInstanceRef, number_format: ClassInstanceRef, ) -> Result<()> { - if number_format.is_null() { - return Err(jvm.exception("java/lang/NullPointerException", "numberFormat").await); - } jvm.put_field( &mut this, "java/text/DateFormat", @@ -564,23 +558,35 @@ impl DateFormat { async fn get_time_zone(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result> { let calendar: ClassInstanceRef = jvm.get_field(&this, "java/text/DateFormat", "calendar", "Ljava/util/Calendar;").await?; + if calendar.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "calendar").await); + } jvm.invoke_virtual(&calendar, "java/util/Calendar", "getTimeZone", "()Ljava/util/TimeZone;", ()) .await } async fn set_time_zone(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, time_zone: ClassInstanceRef) -> Result<()> { let calendar: ClassInstanceRef = jvm.get_field(&this, "java/text/DateFormat", "calendar", "Ljava/util/Calendar;").await?; + if calendar.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "calendar").await); + } jvm.invoke_virtual(&calendar, "java/util/Calendar", "setTimeZone", "(Ljava/util/TimeZone;)V", (time_zone,)) .await } async fn is_lenient(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { let calendar: ClassInstanceRef = jvm.get_field(&this, "java/text/DateFormat", "calendar", "Ljava/util/Calendar;").await?; + if calendar.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "calendar").await); + } jvm.invoke_virtual(&calendar, "java/util/Calendar", "isLenient", "()Z", ()).await } async fn set_lenient(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, lenient: bool) -> Result<()> { let calendar: ClassInstanceRef = jvm.get_field(&this, "java/text/DateFormat", "calendar", "Ljava/util/Calendar;").await?; + if calendar.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "calendar").await); + } jvm.invoke_virtual(&calendar, "java/util/Calendar", "setLenient", "(Z)V", (lenient,)) .await } @@ -590,11 +596,17 @@ impl DateFormat { let number_format: ClassInstanceRef = jvm .get_field(&this, "java/text/DateFormat", "numberFormat", "Ljava/text/NumberFormat;") .await?; + if calendar.is_null() || number_format.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "calendar or numberFormat").await); + } let mut cloned: ClassInstanceRef = jvm.shallow_clone(&this)?.into(); let mut cloned_calendar: ClassInstanceRef = jvm.shallow_clone(&calendar)?.into(); let fields: ClassInstanceRef> = jvm.get_field(&calendar, "java/util/Calendar", "fields", "[I").await?; let cloned_fields: ClassInstanceRef> = jvm.shallow_clone(&fields)?.into(); let time_zone: ClassInstanceRef = jvm.get_field(&calendar, "java/util/Calendar", "timeZone", "Ljava/util/TimeZone;").await?; + if time_zone.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "timeZone").await); + } let cloned_time_zone: ClassInstanceRef = jvm.shallow_clone(&time_zone)?.into(); jvm.put_field(&mut cloned_calendar, "java/util/Calendar", "fields", "[I", cloned_fields) .await?; diff --git a/rustjava-runtime/src/classes/java/text/simple_date_format.rs b/rustjava-runtime/src/classes/java/text/simple_date_format.rs index 0a9d2649..36fbd858 100644 --- a/rustjava-runtime/src/classes/java/text/simple_date_format.rs +++ b/rustjava-runtime/src/classes/java/text/simple_date_format.rs @@ -9,7 +9,7 @@ use crate::{ RuntimeClassProto, RuntimeContext, classes::java::{ lang::{String, StringBuffer}, - text::{FieldPosition, ParsePosition}, + text::{FieldPosition, NumberFormat, ParsePosition}, util::{Calendar, Date, Locale, TimeZone}, }, }; @@ -20,6 +20,17 @@ enum DateToken { Field(char, usize), } +enum DateParseError { + Invalid(usize), + NullNumberFormat, +} + +impl From for DateParseError { + fn from(index: usize) -> Self { + Self::Invalid(index) + } +} + // public class java.text.SimpleDateFormat pub struct SimpleDateFormat; @@ -123,7 +134,13 @@ impl SimpleDateFormat { .is_some_and(|candidate| candidate.iter().zip(value).all(|(left, right)| left.eq_ignore_ascii_case(&right))) } - fn parse_timestamp(tokens: &[DateToken], characters: &[char], start: usize, default_offset: i32) -> core::result::Result<(i64, usize), usize> { + fn parse_timestamp( + tokens: &[DateToken], + characters: &[char], + start: usize, + default_offset: i32, + number_format_is_null: bool, + ) -> core::result::Result<(i64, usize), DateParseError> { let months = [ ("January", "Jan"), ("February", "Feb"), @@ -169,10 +186,18 @@ impl SimpleDateFormat { DateToken::Literal(literal) => { let value: Vec = literal.chars().collect(); if !characters[index..].starts_with(&value) { - return Err(index); + return Err(DateParseError::Invalid(index)); } index += value.len(); } + DateToken::Field(character, count) + if number_format_is_null + && characters[index..].iter().any(|character| !matches!(character, ' ' | '\t')) + && (matches!(character, 'y' | 'd' | 'H' | 'k' | 'h' | 'K' | 'm' | 's' | 'S' | 'D' | 'F' | 'w' | 'W') + || (*character == 'M' && *count < 3)) => + { + return Err(DateParseError::NullNumberFormat); + } DateToken::Field(character, count) => match character { 'G' => { if Self::starts_with_ignore_ascii_case(characters, index, "AD") { @@ -182,7 +207,7 @@ impl SimpleDateFormat { era = 0; index += 2; } else { - return Err(index); + return Err(DateParseError::Invalid(index)); } } 'y' => { @@ -203,7 +228,7 @@ impl SimpleDateFormat { } } let Some((value, length)) = parsed else { - return Err(index); + return Err(DateParseError::Invalid(index)); }; month = value; month_set = true; @@ -221,7 +246,7 @@ impl SimpleDateFormat { 'k' => { let value = Self::parse_number(characters, &mut index, if *count > 1 { Some(*count) } else { None }).ok_or(index)?; if !(1..=24).contains(&value) { - return Err(index); + return Err(DateParseError::Invalid(index)); } hour = value % 24; } @@ -246,7 +271,7 @@ impl SimpleDateFormat { } } let Some(length) = consumed else { - return Err(index); + return Err(DateParseError::Invalid(index)); }; index += length; } @@ -262,7 +287,7 @@ impl SimpleDateFormat { am_pm = 1; index += 2; } else { - return Err(index); + return Err(DateParseError::Invalid(index)); } } 'z' => { @@ -271,7 +296,7 @@ impl SimpleDateFormat { { index += 3; } else { - return Err(index); + return Err(DateParseError::Invalid(index)); } offset = 0; if index < characters.len() && matches!(characters[index], '+' | '-') { @@ -279,17 +304,17 @@ impl SimpleDateFormat { index += 1; let hours = Self::parse_number(characters, &mut index, Some(2)).ok_or(index)?; if index >= characters.len() || characters[index] != ':' { - return Err(index); + return Err(DateParseError::Invalid(index)); } index += 1; let minutes = Self::parse_number(characters, &mut index, Some(2)).ok_or(index)?; if hours > 23 || minutes > 59 { - return Err(index); + return Err(DateParseError::Invalid(index)); } offset = sign * (hours * 60 + minutes) * 60 * 1000; } } - _ => return Err(index), + _ => return Err(DateParseError::Invalid(index)), }, } } @@ -298,7 +323,7 @@ impl SimpleDateFormat { hour = match kind { 'h' if (1..=12).contains(&value) => value % 12 + am_pm * 12, 'K' if (0..=11).contains(&value) => value + am_pm * 12, - _ => return Err(index), + _ => return Err(DateParseError::Invalid(index)), }; } if era == 0 { @@ -306,7 +331,7 @@ impl SimpleDateFormat { } if let Some(ordinal) = ordinal.filter(|_| !month_set && !day_set) { let Some(date) = NaiveDate::from_yo_opt(year, ordinal as u32) else { - return Err(index); + return Err(DateParseError::Invalid(index)); }; month = date.month() as i32; day = date.day() as i32; @@ -318,20 +343,20 @@ impl SimpleDateFormat { || !(0..=59).contains(&second) || !(0..=999).contains(&millisecond) { - return Err(index); + return Err(DateParseError::Invalid(index)); } let Some(date_time) = Utc .with_ymd_and_hms(year, month as u32, day as u32, hour as u32, minute as u32, second as u32) .single() else { - return Err(index); + return Err(DateParseError::Invalid(index)); }; let Some(timestamp) = date_time .timestamp_millis() .checked_add(i64::from(millisecond)) .and_then(|value| value.checked_sub(i64::from(offset))) else { - return Err(index); + return Err(DateParseError::Invalid(index)); }; Ok((timestamp, index)) } @@ -424,6 +449,12 @@ impl SimpleDateFormat { if date.is_null() || buffer.is_null() || position.is_null() { return Err(jvm.exception("java/lang/NullPointerException", "date, buffer, or position").await); } + let number_format: ClassInstanceRef = jvm + .get_field(&this, "java/text/DateFormat", "numberFormat", "Ljava/text/NumberFormat;") + .await?; + if number_format.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "numberFormat").await); + } let pattern: ClassInstanceRef = jvm .get_field(&this, "java/text/SimpleDateFormat", "pattern", "Ljava/lang/String;") .await?; @@ -435,9 +466,15 @@ impl SimpleDateFormat { let calendar: ClassInstanceRef = jvm .get_field(&this, "java/text/SimpleDateFormat", "calendar", "Ljava/util/Calendar;") .await?; + if calendar.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "calendar").await); + } let time_zone: ClassInstanceRef = jvm .invoke_virtual(&calendar, "java/util/Calendar", "getTimeZone", "()Ljava/util/TimeZone;", ()) .await?; + if time_zone.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "timeZone").await); + } let offset: i32 = jvm.invoke_virtual(&time_zone, "java/util/TimeZone", "getRawOffset", "()I", ()).await?; let Some(adjusted) = time.checked_add(i64::from(offset)) else { return Err(jvm.exception("java/lang/IllegalArgumentException", "date out of range").await); @@ -588,11 +625,20 @@ impl SimpleDateFormat { let calendar: ClassInstanceRef = jvm .get_field(&this, "java/text/SimpleDateFormat", "calendar", "Ljava/util/Calendar;") .await?; + if calendar.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "calendar").await); + } let time_zone: ClassInstanceRef = jvm .invoke_virtual(&calendar, "java/util/Calendar", "getTimeZone", "()Ljava/util/TimeZone;", ()) .await?; + if time_zone.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "timeZone").await); + } let offset: i32 = jvm.invoke_virtual(&time_zone, "java/util/TimeZone", "getRawOffset", "()I", ()).await?; - match Self::parse_timestamp(&tokens, &characters, start_index, offset) { + let number_format: ClassInstanceRef = jvm + .get_field(&this, "java/text/DateFormat", "numberFormat", "Ljava/text/NumberFormat;") + .await?; + match Self::parse_timestamp(&tokens, &characters, start_index, offset, number_format.is_null()) { Ok((timestamp, index)) => { let _: () = jvm .invoke_virtual(&position, "java/text/ParsePosition", "setIndex", "(I)V", (utf16_indices[index] as i32,)) @@ -602,7 +648,8 @@ impl SimpleDateFormat { .await?; Ok(jvm.new_class("java/util/Date", "(J)V", (timestamp,)).await?.into()) } - Err(error_index) => { + Err(DateParseError::NullNumberFormat) => Err(jvm.exception("java/lang/NullPointerException", "numberFormat").await), + Err(DateParseError::Invalid(error_index)) => { let _: () = jvm .invoke_virtual( &position, diff --git a/rustjava-runtime/src/classes/java/util/calendar.rs b/rustjava-runtime/src/classes/java/util/calendar.rs index 6857fb22..155ae327 100644 --- a/rustjava-runtime/src/classes/java/util/calendar.rs +++ b/rustjava-runtime/src/classes/java/util/calendar.rs @@ -159,12 +159,13 @@ impl Calendar { async fn set_time_zone(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, time_zone: ClassInstanceRef) -> Result<()> { tracing::debug!("java.util.Calendar::setTimeZone({this:?}, {time_zone:?})"); - if time_zone.is_null() { - return Err(jvm.exception("java/lang/NullPointerException", "timeZone").await); - } + let has_time_zone = !time_zone.is_null(); jvm.put_field(&mut this, "java/util/Calendar", "timeZone", "Ljava/util/TimeZone;", time_zone) .await?; - jvm.invoke_virtual(&this, "java/util/Calendar", "computeFields", "()V", ()).await + if has_time_zone { + let _: () = jvm.invoke_virtual(&this, "java/util/Calendar", "computeFields", "()V", ()).await?; + } + Ok(()) } async fn is_lenient(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { @@ -183,6 +184,9 @@ impl Calendar { if other.is_null() || !jvm.is_instance(&**other, "java/util/Calendar") { return Ok(false); } + if this.identity() == other.identity() { + return Ok(true); + } let other: ClassInstanceRef = ClassInstanceRef::new(other.instance); let time: i64 = jvm.get_field(&this, "java/util/Calendar", "time", "J").await?; @@ -199,6 +203,9 @@ impl Calendar { let time_zone: ClassInstanceRef = jvm.get_field(&this, "java/util/Calendar", "timeZone", "Ljava/util/TimeZone;").await?; let other_time_zone: ClassInstanceRef = jvm.get_field(&other, "java/util/Calendar", "timeZone", "Ljava/util/TimeZone;").await?; + if time_zone.is_null() || other_time_zone.is_null() { + return Ok(false); + } let raw_offset: i32 = jvm.invoke_virtual(&time_zone, "java/util/TimeZone", "getRawOffset", "()I", ()).await?; let other_raw_offset: i32 = jvm .invoke_virtual(&other_time_zone, "java/util/TimeZone", "getRawOffset", "()I", ()) @@ -222,6 +229,9 @@ impl Calendar { let time: i64 = jvm.get_field(&this, "java/util/Calendar", "time", "J").await?; let time_zone: ClassInstanceRef = jvm.get_field(&this, "java/util/Calendar", "timeZone", "Ljava/util/TimeZone;").await?; + if time_zone.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "timeZone").await); + } let raw_offset: i32 = jvm.invoke_virtual(&time_zone, "java/util/TimeZone", "getRawOffset", "()I", ()).await?; let id: ClassInstanceRef = jvm .invoke_virtual(&time_zone, "java/util/TimeZone", "getID", "()Ljava/lang/String;", ()) @@ -276,6 +286,11 @@ impl Calendar { async fn get(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef, field: i32) -> Result { tracing::debug!("java.util.Calendar::get({this:?}, {field:?})"); + let time_zone: ClassInstanceRef = jvm.get_field(&this, "java/util/Calendar", "timeZone", "Ljava/util/TimeZone;").await?; + if time_zone.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "timeZone").await); + } + if !(0..17).contains(&field) { return Err(jvm.exception("java/lang/ArrayIndexOutOfBoundsException", "calendar field").await); } diff --git a/rustjava-runtime/src/classes/java/util/gregorian_calendar.rs b/rustjava-runtime/src/classes/java/util/gregorian_calendar.rs index 199cb53b..87c84f71 100644 --- a/rustjava-runtime/src/classes/java/util/gregorian_calendar.rs +++ b/rustjava-runtime/src/classes/java/util/gregorian_calendar.rs @@ -114,6 +114,9 @@ impl GregorianCalendar { let time_zone: ClassInstanceRef = jvm .get_field(&this, "java/util/GregorianCalendar", "timeZone", "Ljava/util/TimeZone;") .await?; + if time_zone.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "timeZone").await); + } let zone_offset: i32 = jvm.invoke_virtual(&time_zone, "java/util/TimeZone", "getRawOffset", "()I", ()).await?; let Some(adjusted_time) = time.checked_add(zone_offset as i64) else { return Err(jvm.exception("java/lang/IllegalArgumentException", "calendar time out of range").await); diff --git a/rustjava-runtime/src/classes/java/util/zip/zip_entry.rs b/rustjava-runtime/src/classes/java/util/zip/zip_entry.rs index ef4146b1..601b4537 100644 --- a/rustjava-runtime/src/classes/java/util/zip/zip_entry.rs +++ b/rustjava-runtime/src/classes/java/util/zip/zip_entry.rs @@ -38,6 +38,10 @@ impl ZipEntry { async fn init(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, name: ClassInstanceRef) -> Result<()> { tracing::debug!("java.util.zip.ZipEntry::({this:?}, {name:?})"); + if name.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "name is null").await); + } + let _: () = jvm.invoke_special(&this, "java/lang/Object", "", "()V", ()).await?; jvm.put_field(&mut this, "java/util/zip/ZipEntry", "name", "Ljava/lang/String;", name) @@ -54,6 +58,10 @@ impl ZipEntry { ) -> Result<()> { tracing::debug!("java.util.zip.ZipEntry::({this:?}, {zip_entry:?})"); + if zip_entry.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "entry is null").await); + } + let _: () = jvm.invoke_special(&this, "java/lang/Object", "", "()V", ()).await?; let name: ClassInstanceRef = jvm.get_field(&zip_entry, "java/util/zip/ZipEntry", "name", "Ljava/lang/String;").await?; diff --git a/rustjava-runtime/src/classes/java/util/zip/zip_file.rs b/rustjava-runtime/src/classes/java/util/zip/zip_file.rs index 71109d5a..d6a304e8 100644 --- a/rustjava-runtime/src/classes/java/util/zip/zip_file.rs +++ b/rustjava-runtime/src/classes/java/util/zip/zip_file.rs @@ -65,6 +65,10 @@ impl ZipFile { async fn init(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef, file: ClassInstanceRef) -> Result<()> { tracing::debug!("java.util.zip.ZipFile::({this:?}, {file:?})"); + if file.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "file is null").await); + } + let _: () = jvm.invoke_special(&this, "java/lang/Object", "", "()V", ()).await?; let length: i64 = jvm.invoke_virtual(&file, "java/io/File", "length", "()J", ()).await?; @@ -140,6 +144,10 @@ impl ZipFile { ) -> Result> { tracing::debug!("java.util.zip.ZipFile::getInputStream({this:?}, {entry:?})"); + if entry.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "entry is null").await); + } + let entry_name = jvm .invoke_virtual(&entry, "java/util/zip/ZipEntry", "getName", "()Ljava/lang/String;", ()) .await?; diff --git a/rustjava-runtime/src/classes/org/rustjava/lang/rust_jar_class_loader.rs b/rustjava-runtime/src/classes/org/rustjava/lang/rust_jar_class_loader.rs index 57b57465..4d17b765 100644 --- a/rustjava-runtime/src/classes/org/rustjava/lang/rust_jar_class_loader.rs +++ b/rustjava-runtime/src/classes/org/rustjava/lang/rust_jar_class_loader.rs @@ -46,6 +46,10 @@ impl RustJarClassLoader { ) -> Result<()> { tracing::debug!("org.rustjava.lang.RustJarClassLoader::({this:?}, {class_paths:?}, {parent:?})"); + if class_paths.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "classPaths").await); + } + let _: () = jvm .invoke_special(&this, "java/lang/ClassLoader", "", "(Ljava/lang/ClassLoader;)V", (parent,)) .await?; @@ -70,6 +74,10 @@ impl RustJarClassLoader { ) -> Result> { tracing::debug!("org.rustjava.lang.RustJarClassLoader::findClass({this:?}, {name:?})"); + if name.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "name").await); + } + let name = JavaLangString::to_rust_string(jvm, &name).await?; let class_paths = jvm .get_field(&this, "org/rustjava/lang/RustJarClassLoader", "classPaths", "[Ljava/lang/String;") @@ -77,6 +85,9 @@ impl RustJarClassLoader { let class_paths: Vec> = jvm.load_array(&class_paths, 0, jvm.array_length(&class_paths).await? as usize).await?; for class_path in class_paths { + if class_path.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "classPath").await); + } let class_path = JavaLangString::to_rust_string(jvm, &class_path).await?; if !class_path.ends_with(".rustjar") { continue; diff --git a/rustjava-runtime/src/classes/org/rustjava/net/file_url_handler.rs b/rustjava-runtime/src/classes/org/rustjava/net/file_url_handler.rs index f85b56d0..67b71097 100644 --- a/rustjava-runtime/src/classes/org/rustjava/net/file_url_handler.rs +++ b/rustjava-runtime/src/classes/org/rustjava/net/file_url_handler.rs @@ -51,6 +51,10 @@ impl FileURLHandler { ) -> Result> { tracing::debug!("org.rustjava.net.FileURLHandler::openConnection({this:?}, {url:?})"); + if url.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "url is null").await); + } + let file: ClassInstanceRef = jvm.invoke_virtual(&url, "java/net/URL", "getFile", "()Ljava/lang/String;", ()).await?; let file: ClassInstanceRef = jvm .invoke_static("org/rustjava/net/URLPath", "decode", "(Ljava/lang/String;)Ljava/lang/String;", (file,)) diff --git a/rustjava-runtime/src/classes/org/rustjava/net/url_path.rs b/rustjava-runtime/src/classes/org/rustjava/net/url_path.rs index fe97afde..7f5099f2 100644 --- a/rustjava-runtime/src/classes/org/rustjava/net/url_path.rs +++ b/rustjava-runtime/src/classes/org/rustjava/net/url_path.rs @@ -48,6 +48,9 @@ impl URLPath { } async fn encode(jvm: &Jvm, _: &mut RuntimeContext, path: ClassInstanceRef) -> Result> { + if path.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "path is null").await); + } let path = JavaLangString::to_rust_string(jvm, &path).await?; Ok( JavaLangString::from_rust_string(jvm, &utf8_percent_encode(&path, Self::ESCAPED).to_string()) @@ -57,6 +60,9 @@ impl URLPath { } async fn decode(jvm: &Jvm, _: &mut RuntimeContext, path: ClassInstanceRef) -> Result> { + if path.is_null() { + return Err(jvm.exception("java/lang/NullPointerException", "path is null").await); + } let path = JavaLangString::to_rust_string(jvm, &path).await?; match percent_decode_str(&path).decode_utf8() { Ok(path) => Ok(JavaLangString::from_rust_string(jvm, &path).await?.into()), diff --git a/rustjava-runtime/tests/classes/java/io/test_buffered_streams.rs b/rustjava-runtime/tests/classes/java/io/test_buffered_streams.rs index 29038731..15193aef 100644 --- a/rustjava-runtime/tests/classes/java/io/test_buffered_streams.rs +++ b/rustjava-runtime/tests/classes/java/io/test_buffered_streams.rs @@ -6,6 +6,24 @@ use rustjava_runtime::classes::java::io::{InputStream, OutputStream, Writer}; use rustjava_runtime::classes::java::lang::Object; use test_utils::test_jvm; +#[tokio::test] +async fn buffered_output_rejects_null_buffer() -> Result<()> { + let jvm = test_jvm().await?; + let output = jvm.new_class("java/io/ByteArrayOutputStream", "()V", ()).await?; + let buffered = jvm + .new_class("java/io/BufferedOutputStream", "(Ljava/io/OutputStream;)V", (output,)) + .await?; + let null: ClassInstanceRef> = None.into(); + let result: Result<()> = jvm + .invoke_virtual(&buffered, "java/io/BufferedOutputStream", "write", "([BII)V", (null, 0, 0)) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("null buffer must throw NullPointerException even for an empty write"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + Ok(()) +} + #[tokio::test] async fn bio_01_bio_02_bio_03_buffered_input_stream_contract() -> Result<()> { let jvm = test_jvm().await?; diff --git a/rustjava-runtime/tests/classes/java/io/test_char_streams.rs b/rustjava-runtime/tests/classes/java/io/test_char_streams.rs index 618affde..8ec1aa32 100644 --- a/rustjava-runtime/tests/classes/java/io/test_char_streams.rs +++ b/rustjava-runtime/tests/classes/java/io/test_char_streams.rs @@ -3,6 +3,46 @@ use jvm::{Array, ClassInstanceRef, JavaChar, JavaError, Result, runtime::JavaLan use rustjava_runtime::classes::java::lang::String; use test_utils::test_jvm; +#[tokio::test] +async fn character_streams_reject_null_buffers_and_strings() -> Result<()> { + use jvm::JavaValue; + + let jvm = test_jvm().await?; + let bytes = jvm.instantiate_array("B", 0).await?; + let input = jvm.new_class("java/io/ByteArrayInputStream", "([B)V", (bytes,)).await?; + let reader = jvm.new_class("java/io/InputStreamReader", "(Ljava/io/InputStream;)V", (input,)).await?; + for (signature, args) in [ + ("([C)I", vec![JavaValue::Object(None)]), + ("([CII)I", vec![JavaValue::Object(None), 0.into(), 0.into()]), + ] { + let result: Result = jvm.invoke_virtual(&reader, "java/io/Reader", "read", signature, args).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("read{signature} must reject null"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + } + let string_writer = jvm.new_class("java/io/StringWriter", "()V", ()).await?; + let output = jvm.new_class("java/io/ByteArrayOutputStream", "()V", ()).await?; + let stream_writer = jvm + .new_class("java/io/OutputStreamWriter", "(Ljava/io/OutputStream;)V", (output,)) + .await?; + for writer in [&string_writer, &stream_writer] { + for (signature, args) in [ + ("([C)V", vec![JavaValue::Object(None)]), + ("([CII)V", vec![JavaValue::Object(None), 0.into(), 0.into()]), + ("(Ljava/lang/String;)V", vec![JavaValue::Object(None)]), + ("(Ljava/lang/String;II)V", vec![JavaValue::Object(None), 0.into(), 0.into()]), + ] { + let result: Result<()> = jvm.invoke_virtual(writer, "java/io/Writer", "write", signature, args).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("write{signature} must reject null"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + } + } + Ok(()) +} + #[tokio::test] async fn cr_01_cr_02_string_reader_contract() -> Result<()> { let jvm = test_jvm().await?; diff --git a/rustjava-runtime/tests/classes/java/io/test_data_input_stream.rs b/rustjava-runtime/tests/classes/java/io/test_data_input_stream.rs index 9f4aa8bd..beca8c0a 100644 --- a/rustjava-runtime/tests/classes/java/io/test_data_input_stream.rs +++ b/rustjava-runtime/tests/classes/java/io/test_data_input_stream.rs @@ -7,6 +7,60 @@ use rustjava_runtime::classes::java::lang::String; use test_utils::test_jvm; +#[tokio::test] +async fn null_input_arguments_and_delegates() -> Result<()> { + use jvm::{JavaError, JavaValue}; + + let jvm = test_jvm().await?; + let input = jvm + .new_class("java/io/DataInputStream", "(Ljava/io/InputStream;)V", [JavaValue::Object(None)]) + .await?; + for (method, signature, args) in [ + ("available", "()I", vec![]), + ("close", "()V", vec![]), + ("reset", "()V", vec![]), + ("markSupported", "()Z", vec![]), + ("mark", "(I)V", vec![1.into()]), + ("skip", "(J)J", vec![1i64.into()]), + ("read", "()I", vec![]), + ("read", "([B)I", vec![JavaValue::Object(None)]), + ("read", "([BII)I", vec![JavaValue::Object(None), 0.into(), 1.into()]), + ("readByte", "()B", vec![]), + ("skipBytes", "(I)I", vec![1.into()]), + ("readFully", "([B)V", vec![JavaValue::Object(None)]), + ("readFully", "([BII)V", vec![JavaValue::Object(None), 0.into(), 0.into()]), + ] { + let result: Result = jvm.invoke_virtual(&input, "java/io/DataInputStream", method, signature, args).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("{method}{signature} must throw NullPointerException"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException"), "{method}{signature}"); + } + let result: Result = jvm + .invoke_static( + "java/io/DataInputStream", + "readUTF", + "(Ljava/io/DataInput;)Ljava/lang/String;", + [JavaValue::Object(None)], + ) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("static readUTF must reject null"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + for amount in [0, -1] { + let skipped: i32 = jvm + .invoke_virtual(&input, "java/io/DataInputStream", "skipBytes", "(I)I", (amount,)) + .await?; + assert_eq!(skipped, 0); + } + let empty = jvm.instantiate_array("B", 0).await?; + let _: () = jvm + .invoke_virtual(&input, "java/io/DataInputStream", "readFully", "([BII)V", (empty, 0, 0)) + .await?; + Ok(()) +} + #[tokio::test] async fn test_data_input_stream() -> Result<()> { let jvm = test_jvm().await?; diff --git a/rustjava-runtime/tests/classes/java/io/test_data_output_stream.rs b/rustjava-runtime/tests/classes/java/io/test_data_output_stream.rs index 0f3e18f5..9b47d17d 100644 --- a/rustjava-runtime/tests/classes/java/io/test_data_output_stream.rs +++ b/rustjava-runtime/tests/classes/java/io/test_data_output_stream.rs @@ -4,6 +4,72 @@ use jvm::{Array, ClassInstanceRef, JavaError, Result, runtime::JavaLangString}; use test_utils::test_jvm; +#[tokio::test] +async fn null_output_arguments_and_delegates() -> Result<()> { + use jvm::JavaValue; + + let jvm = test_jvm().await?; + let output = jvm + .new_class("java/io/DataOutputStream", "(Ljava/io/OutputStream;)V", [JavaValue::Object(None)]) + .await?; + let text = JavaLangString::from_rust_string(&jvm, "x").await?; + for (method, signature, args) in [ + ("write", "(I)V", vec![1.into()]), + ("writeByte", "(I)V", vec![1.into()]), + ("writeShort", "(I)V", vec![1.into()]), + ("writeInt", "(I)V", vec![1.into()]), + ("writeLong", "(J)V", vec![1i64.into()]), + ("writeBytes", "(Ljava/lang/String;)V", vec![text.clone().into()]), + ("writeChars", "(Ljava/lang/String;)V", vec![text.clone().into()]), + ("writeBytes", "(Ljava/lang/String;)V", vec![JavaValue::Object(None)]), + ("writeChars", "(Ljava/lang/String;)V", vec![JavaValue::Object(None)]), + ("writeUTF", "(Ljava/lang/String;)V", vec![JavaValue::Object(None)]), + ("flush", "()V", vec![]), + ("close", "()V", vec![]), + ] { + let result: Result = jvm.invoke_virtual(&output, "java/io/DataOutputStream", method, signature, args).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("{method}{signature} must throw NullPointerException"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException"), "{method}{signature}"); + } + let empty = JavaLangString::from_rust_string(&jvm, "").await?; + for method in ["writeBytes", "writeChars"] { + let _: () = jvm + .invoke_virtual(&output, "java/io/DataOutputStream", method, "(Ljava/lang/String;)V", (empty.clone(),)) + .await?; + } + let filter = jvm + .new_class("java/io/FilterOutputStream", "(Ljava/io/OutputStream;)V", [JavaValue::Object(None)]) + .await?; + let bytes = jvm.instantiate_array("B", 0).await?; + let _: () = jvm + .invoke_virtual(&filter, "java/io/FilterOutputStream", "write", "([BII)V", (bytes.clone(), 0, 0)) + .await?; + for (offset, length) in [(1, 0), (-1, 0), (0, -1)] { + let result: Result<()> = jvm + .invoke_virtual(&filter, "java/io/FilterOutputStream", "write", "([BII)V", (bytes.clone(), offset, length)) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("invalid empty range must throw IndexOutOfBoundsException"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/IndexOutOfBoundsException")); + } + for (method, signature, args) in [ + ("write", "(I)V", vec![1.into()]), + ("flush", "()V", vec![]), + ("write", "([BII)V", vec![JavaValue::Object(None), 0.into(), 0.into()]), + ("write", "([BII)V", vec![jvm.instantiate_array("B", 1).await?.into(), 0.into(), 1.into()]), + ] { + let result: Result = jvm.invoke_virtual(&filter, "java/io/FilterOutputStream", method, signature, args).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("{method}{signature} must throw NullPointerException"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + } + Ok(()) +} + #[tokio::test] async fn test_data_output_stream() -> Result<()> { let jvm = test_jvm().await?; diff --git a/rustjava-runtime/tests/classes/java/io/test_file_input_stream.rs b/rustjava-runtime/tests/classes/java/io/test_file_input_stream.rs index 1f93fdd1..af7e95ac 100644 --- a/rustjava-runtime/tests/classes/java/io/test_file_input_stream.rs +++ b/rustjava-runtime/tests/classes/java/io/test_file_input_stream.rs @@ -4,6 +4,84 @@ use jvm::{Result, runtime::JavaLangString}; use test_utils::test_jvm_filesystem; +#[tokio::test] +async fn file_streams_reject_null_arguments() -> Result<()> { + use jvm::{JavaError, JavaValue}; + + let jvm = test_jvm_filesystem([("test.txt".into(), b"hello".to_vec())].into_iter().collect()).await?; + for class in ["java/io/FileInputStream", "java/io/FileOutputStream"] { + for descriptor in ["(Ljava/io/File;)V", "(Ljava/io/FileDescriptor;)V"] { + let result = jvm.new_class(class, descriptor, [JavaValue::Object(None)]).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("{class}{descriptor} must reject null"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + } + } + let result = jvm.new_class("java/io/File", "(Ljava/lang/String;)V", [JavaValue::Object(None)]).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("File must reject a null path"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + + let result = jvm + .new_class("java/io/FileOutputStream", "(Ljava/io/File;Z)V", [JavaValue::Object(None), true.into()]) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("append constructor must reject a null file"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + + let path = JavaLangString::from_rust_string(&jvm, "test.txt").await?; + let file = jvm.new_class("java/io/File", "(Ljava/lang/String;)V", (path,)).await?; + let input = jvm.new_class("java/io/FileInputStream", "(Ljava/io/File;)V", (file.clone(),)).await?; + let descriptor = jvm.new_class("java/io/FileDescriptor", "()V", ()).await?; + let output = jvm + .new_class("java/io/FileOutputStream", "(Ljava/io/FileDescriptor;)V", (descriptor,)) + .await?; + for (stream, class, method, descriptor) in [ + (&input, "java/io/FileInputStream", "read", "([BII)I"), + (&output, "java/io/FileOutputStream", "write", "([BII)V"), + ] { + for length in [0, 1] { + let result: Result = jvm + .invoke_virtual(stream, class, method, descriptor, [JavaValue::Object(None), 0.into(), length.into()]) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("{class}.{method} must reject null"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + } + } + let first: i32 = jvm.invoke_virtual(&input, "java/io/FileInputStream", "read", "()I", ()).await?; + assert_eq!(first, b'h' as i32); + Ok(()) +} + +#[tokio::test] +async fn unavailable_standard_streams_have_nonnull_descriptors() -> Result<()> { + use jvm::{ClassInstanceRef, JavaError}; + use rustjava_runtime::classes::java::io::FileDescriptor; + + let jvm = test_jvm_filesystem(Default::default()).await?; + for (name, class, method, signature) in [ + ("in", "java/io/FileInputStream", "read", "()I"), + ("out", "java/io/FileOutputStream", "write", "(I)V"), + ("err", "java/io/FileOutputStream", "write", "(I)V"), + ] { + let descriptor: ClassInstanceRef = jvm.get_static_field("java/io/FileDescriptor", name, "Ljava/io/FileDescriptor;").await?; + assert!(!descriptor.is_null(), "{name}"); + let stream = jvm.new_class(class, "(Ljava/io/FileDescriptor;)V", (descriptor,)).await?; + let args = if name == "in" { vec![] } else { vec![1.into()] }; + let result: Result = jvm.invoke_virtual(&stream, class, method, signature, args).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("unavailable {name} must report IOException"); + }; + assert!(jvm.is_instance(&*exception, "java/io/IOException")); + } + Ok(()) +} + #[tokio::test] async fn test_file_input_stream_read_buf() -> Result<()> { let filesystem = [("test.txt".into(), b"hello world".to_vec())]; diff --git a/rustjava-runtime/tests/classes/java/io/test_random_access_file.rs b/rustjava-runtime/tests/classes/java/io/test_random_access_file.rs index 9dcd20e8..e18fe07a 100644 --- a/rustjava-runtime/tests/classes/java/io/test_random_access_file.rs +++ b/rustjava-runtime/tests/classes/java/io/test_random_access_file.rs @@ -4,6 +4,49 @@ use jvm::{Result, runtime::JavaLangString}; use test_utils::test_jvm_filesystem; +#[tokio::test] +async fn random_access_file_rejects_null_arguments() -> Result<()> { + use jvm::{JavaError, JavaValue}; + + let jvm = test_jvm_filesystem([("test.txt".into(), b"hello".to_vec())].into_iter().collect()).await?; + let path = JavaLangString::from_rust_string(&jvm, "test.txt").await?; + let mode = JavaLangString::from_rust_string(&jvm, "r").await?; + let file = jvm.new_class("java/io/File", "(Ljava/lang/String;)V", (path.clone(),)).await?; + for (signature, args) in [ + ("(Ljava/lang/String;Ljava/lang/String;)V", [JavaValue::Object(None), mode.clone().into()]), + ("(Ljava/lang/String;Ljava/lang/String;)V", [path.clone().into(), JavaValue::Object(None)]), + ("(Ljava/io/File;Ljava/lang/String;)V", [JavaValue::Object(None), mode.clone().into()]), + ("(Ljava/io/File;Ljava/lang/String;)V", [file.into(), JavaValue::Object(None)]), + ] { + let result = jvm.new_class("java/io/RandomAccessFile", signature, args).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("{signature} must reject null"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + } + let stream = jvm + .new_class("java/io/RandomAccessFile", "(Ljava/lang/String;Ljava/lang/String;)V", (path, mode)) + .await?; + for (method, signature, args) in [ + ("read", "([B)I", vec![JavaValue::Object(None)]), + ("write", "([B)V", vec![JavaValue::Object(None)]), + ("read", "([BII)I", vec![JavaValue::Object(None), 0.into(), 0.into()]), + ("read", "([BII)I", vec![JavaValue::Object(None), 0.into(), 1.into()]), + ("write", "([BII)V", vec![JavaValue::Object(None), 0.into(), 0.into()]), + ] { + let result: Result = jvm.invoke_virtual(&stream, "java/io/RandomAccessFile", method, signature, args).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("{method}{signature} must reject null"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + } + let position: i64 = jvm + .invoke_virtual(&stream, "java/io/RandomAccessFile", "getFilePointer", "()J", ()) + .await?; + assert_eq!(position, 0); + Ok(()) +} + #[tokio::test] async fn test_random_access_file() -> Result<()> { let filesystem = [("test.txt".into(), b"hello world".to_vec())]; diff --git a/rustjava-runtime/tests/classes/java/lang/test_class.rs b/rustjava-runtime/tests/classes/java/lang/test_class.rs index bdaedced..782f8191 100644 --- a/rustjava-runtime/tests/classes/java/lang/test_class.rs +++ b/rustjava-runtime/tests/classes/java/lang/test_class.rs @@ -6,7 +6,7 @@ use jvm_types::MethodAccessFlags; use rustjava_runtime::{ Runtime, classes::java::{ - lang::{Class, ClassLoader, String}, + lang::{Class, ClassLoader, Object, String}, net::URL, }, get_bootstrap_class_loader, @@ -506,6 +506,28 @@ async fn test_base_class_loader_delegates_to_bootstrap_and_find_class_throws() - .await? .into(); + let loaded: ClassInstanceRef = jvm + .invoke_virtual( + &loader, + "java/lang/ClassLoader", + "findLoadedClass", + "(Ljava/lang/String;)Ljava/lang/Class;", + (None,), + ) + .await?; + assert!(loaded.is_null()); + for (method, descriptor) in [ + ("loadClass", "(Ljava/lang/String;)Ljava/lang/Class;"), + ("getResource", "(Ljava/lang/String;)Ljava/net/URL;"), + ("getResourceAsStream", "(Ljava/lang/String;)Ljava/io/InputStream;"), + ] { + let result: Result> = jvm.invoke_virtual(&loader, "java/lang/ClassLoader", method, descriptor, (None,)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("ClassLoader.{method} must reject null"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/NullPointerException")); + } + let name = JavaLangString::from_rust_string(&jvm, "java/util/Random").await?; let class: ClassInstanceRef = jvm .invoke_virtual( diff --git a/rustjava-runtime/tests/classes/java/lang/test_integer.rs b/rustjava-runtime/tests/classes/java/lang/test_integer.rs index 3fc7098e..b055694a 100644 --- a/rustjava-runtime/tests/classes/java/lang/test_integer.rs +++ b/rustjava-runtime/tests/classes/java/lang/test_integer.rs @@ -3,6 +3,68 @@ use rustjava_runtime::classes::java::lang::{Integer, Object, String}; use test_utils::test_jvm; +#[tokio::test] +async fn test_integral_wrapper_null_strings() -> Result<()> { + let jvm = test_jvm().await?; + + for (class, parse, primitive) in [ + ("java/lang/Byte", "parseByte", "B"), + ("java/lang/Short", "parseShort", "S"), + ("java/lang/Integer", "parseInt", "I"), + ("java/lang/Long", "parseLong", "J"), + ] { + for (method, descriptor, radix, exception_class) in [ + ( + parse, + format!("(Ljava/lang/String;){primitive}"), + false, + "java/lang/NumberFormatException", + ), + ( + parse, + format!("(Ljava/lang/String;I){primitive}"), + true, + "java/lang/NumberFormatException", + ), + ( + "valueOf", + format!("(Ljava/lang/String;)L{class};"), + false, + "java/lang/NumberFormatException", + ), + ( + "valueOf", + format!("(Ljava/lang/String;I)L{class};"), + true, + "java/lang/NumberFormatException", + ), + ( + "decode", + format!("(Ljava/lang/String;)L{class};"), + false, + "java/lang/NullPointerException", + ), + ] { + let result: Result = if radix { + jvm.invoke_static(class, method, &descriptor, (None, 10)).await + } else { + jvm.invoke_static(class, method, &descriptor, (None,)).await + }; + let Err(JavaError::JavaException(exception)) = result else { + panic!("{class}.{method}{descriptor} must throw {exception_class}"); + }; + assert!(jvm.is_instance(&*exception, exception_class), "{class}.{method}{descriptor}"); + } + let result = jvm.new_class(class, "(Ljava/lang/String;)V", (None,)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("{class}(null) must throw NumberFormatException"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NumberFormatException")); + } + + Ok(()) +} + #[tokio::test] async fn test_parse_int() -> Result<()> { let jvm = test_jvm().await?; diff --git a/rustjava-runtime/tests/classes/java/lang/test_short.rs b/rustjava-runtime/tests/classes/java/lang/test_short.rs index dfa1e59f..af798a27 100644 --- a/rustjava-runtime/tests/classes/java/lang/test_short.rs +++ b/rustjava-runtime/tests/classes/java/lang/test_short.rs @@ -59,12 +59,6 @@ async fn test_short_api() -> Result<()> { 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, &value.class_definition().name(), "compareTo", "(Ljava/lang/Short;)I", (None,)) .await; diff --git a/rustjava-runtime/tests/classes/java/lang/test_string.rs b/rustjava-runtime/tests/classes/java/lang/test_string.rs index 153e207f..b3813581 100644 --- a/rustjava-runtime/tests/classes/java/lang/test_string.rs +++ b/rustjava-runtime/tests/classes/java/lang/test_string.rs @@ -462,6 +462,14 @@ async fn test_string_concat() -> Result<()> { assert_eq!(string, "test1test2"); + let result: Result> = jvm + .invoke_virtual(&string1, "java/lang/String", "concat", "(Ljava/lang/String;)Ljava/lang/String;", (None,)) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("concat must reject null"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/NullPointerException")); + Ok(()) } @@ -685,6 +693,14 @@ async fn test_get_chars() -> Result<()> { let string = JavaLangString::from_rust_string(&jvm, "Hello, 테스트!").await?; + let result: Result<()> = jvm + .invoke_virtual(&string, "java/lang/String", "getChars", "(II[CI)V", (0, 0, None, 0)) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("getChars must reject a null destination even for an empty range"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/NullPointerException")); + let char_array = jvm.instantiate_array("[C", 11).await?; let _: () = jvm @@ -913,6 +929,33 @@ async fn test_init_empty() -> Result<()> { Ok(()) } +#[tokio::test] +async fn test_init_rejects_null() -> Result<()> { + let jvm = test_jvm().await?; + let bytes: ClassInstanceRef> = None.into(); + let charset = JavaLangString::from_rust_string(&jvm, "UTF-8").await?; + + for result in [ + jvm.new_class("java/lang/String", "([B)V", (bytes.clone(),)).await, + jvm.new_class("java/lang/String", "([BII)V", (bytes.clone(), 0, 0)).await, + jvm.new_class("java/lang/String", "([BLjava/lang/String;)V", (bytes.clone(), charset.clone())) + .await, + jvm.new_class("java/lang/String", "([BIILjava/lang/String;)V", (bytes, 0, 0, charset)) + .await, + jvm.new_class("java/lang/String", "([C)V", (None,)).await, + jvm.new_class("java/lang/String", "([CII)V", (None, 0, 0)).await, + jvm.new_class("java/lang/String", "(Ljava/lang/String;)V", (None,)).await, + jvm.new_class("java/lang/String", "(Ljava/lang/StringBuffer;)V", (None,)).await, + ] { + let Err(JavaError::JavaException(exception)) = result else { + panic!("String constructors must reject null"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/NullPointerException")); + } + + Ok(()) +} + #[tokio::test] async fn test_init_byte_array_charset() -> Result<()> { let jvm = test_jvm().await?; diff --git a/rustjava-runtime/tests/classes/java/lang/test_system.rs b/rustjava-runtime/tests/classes/java/lang/test_system.rs index c8f53c68..7a665ab1 100644 --- a/rustjava-runtime/tests/classes/java/lang/test_system.rs +++ b/rustjava-runtime/tests/classes/java/lang/test_system.rs @@ -12,6 +12,27 @@ use rustjava_runtime::{ }; use test_utils::{TestRuntime, create_test_jvm}; +#[tokio::test] +async fn test_arraycopy_rejects_null_arrays() -> Result<()> { + let jvm = create_test_jvm(TestRuntime::new(BTreeMap::new())).await?; + let array = jvm.instantiate_array("B", 1).await?; + for (source, destination) in [(None, Some(array.clone())), (Some(array), None), (None, None)] { + let result: Result<()> = jvm + .invoke_static( + "java/lang/System", + "arraycopy", + "(Ljava/lang/Object;ILjava/lang/Object;II)V", + (source, 0, destination, 0, 0), + ) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("arraycopy must reject null arrays even for zero length"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/NullPointerException")); + } + Ok(()) +} + #[tokio::test] async fn test_system_time_yield_and_exit_runtime_contract() -> Result<()> { let runtime = TestRuntime::new(BTreeMap::new()); diff --git a/rustjava-runtime/tests/classes/java/lang/test_throwable.rs b/rustjava-runtime/tests/classes/java/lang/test_throwable.rs index d7b34138..650698df 100644 --- a/rustjava-runtime/tests/classes/java/lang/test_throwable.rs +++ b/rustjava-runtime/tests/classes/java/lang/test_throwable.rs @@ -42,6 +42,17 @@ async fn test_to_string() -> Result<()> { async fn test_stacktrace() -> Result<()> { let jvm = test_jvm().await?; + let throwable = jvm.new_class("java/lang/Throwable", "()V", ()).await?; + for descriptor in ["(Ljava/io/PrintStream;)V", "(Ljava/io/PrintWriter;)V"] { + let result: Result<()> = jvm + .invoke_virtual(&throwable, "java/lang/Throwable", "printStackTrace", descriptor, (None,)) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("printStackTrace must reject a null output"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/NullPointerException")); + } + // get exception by creating invalid url let url_string = JavaLangString::from_rust_string(&jvm, "invalid://invalid").await?; let url: Result> = jvm.new_class("java/net/URL", "(Ljava/lang/String;)V", (url_string,)).await; diff --git a/rustjava-runtime/tests/classes/java/net/mod.rs b/rustjava-runtime/tests/classes/java/net/mod.rs index 111692e0..3a9fdd55 100644 --- a/rustjava-runtime/tests/classes/java/net/mod.rs +++ b/rustjava-runtime/tests/classes/java/net/mod.rs @@ -1,2 +1,3 @@ mod test_jar_url_connection; +mod test_url; mod test_url_class_loader; diff --git a/rustjava-runtime/tests/classes/java/net/test_url.rs b/rustjava-runtime/tests/classes/java/net/test_url.rs new file mode 100644 index 00000000..bd822919 --- /dev/null +++ b/rustjava-runtime/tests/classes/java/net/test_url.rs @@ -0,0 +1,105 @@ +use jvm::{ClassInstanceRef, JavaError, Result, runtime::JavaLangString}; +use rustjava_runtime::classes::java::net::URLConnection; +use test_utils::test_jvm; + +#[tokio::test] +async fn test_null_constructor_arguments() -> Result<()> { + let jvm = test_jvm().await?; + for result in [ + jvm.new_class("java/net/URL", "(Ljava/lang/String;)V", (None,)).await, + jvm.new_class("java/net/URL", "(Ljava/net/URL;Ljava/lang/String;)V", (None, None)).await, + jvm.new_class( + "java/net/URL", + "(Ljava/net/URL;Ljava/lang/String;Ljava/net/URLStreamHandler;)V", + (None, None, None), + ) + .await, + ] { + let Err(JavaError::JavaException(exception)) = result else { + panic!("expected MalformedURLException for null spec"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/net/MalformedURLException")); + } + let protocol = JavaLangString::from_rust_string(&jvm, "file").await?; + let file = JavaLangString::from_rust_string(&jvm, "test.txt").await?; + for (protocol, file) in [(None, Some(file.clone())), (Some(protocol.clone()), None)] { + let result = jvm + .new_class( + "java/net/URL", + "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V", + (protocol, None, file), + ) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("expected NullPointerException for null protocol or file"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/NullPointerException")); + } + jvm.new_class( + "java/net/URL", + "(Ljava/lang/String;Ljava/lang/String;ILjava/lang/String;Ljava/net/URLStreamHandler;)V", + (protocol, None, -1, file, None), + ) + .await?; + let spec = JavaLangString::from_rust_string(&jvm, "file:test.txt").await?; + jvm.new_class( + "java/net/URL", + "(Ljava/net/URL;Ljava/lang/String;Ljava/net/URLStreamHandler;)V", + (None, spec, None), + ) + .await?; + Ok(()) +} + +#[tokio::test] +async fn test_handler_null_arguments() -> Result<()> { + let jvm = test_jvm().await?; + let handler = jvm.new_class("org/rustjava/net/FileURLHandler", "()V", ()).await?; + let spec = JavaLangString::from_rust_string(&jvm, "file:test.txt").await?; + let url = jvm.new_class("java/net/URL", "(Ljava/lang/String;)V", (spec.clone(),)).await?; + for (url, spec) in [(None, Some(spec)), (Some(url), None)] { + let result: Result<()> = jvm + .invoke_virtual( + &handler, + "java/net/URLStreamHandler", + "parseURL", + "(Ljava/net/URL;Ljava/lang/String;II)V", + (url, spec, 0, 13), + ) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("expected NullPointerException for null parseURL argument"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/NullPointerException")); + } + let result: Result<()> = jvm + .invoke_virtual( + &handler, + "java/net/URLStreamHandler", + "setURL", + "(Ljava/net/URL;Ljava/lang/String;Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;)V", + (None, None, None, -1, None, None), + ) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("expected NullPointerException for null setURL target"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/NullPointerException")); + for class in ["org/rustjava/net/FileURLHandler", "org/rustjava/net/JarURLHandler"] { + let handler = jvm.new_class(class, "()V", ()).await?; + let result: Result> = jvm + .invoke_virtual( + &handler, + "java/net/URLStreamHandler", + "openConnection", + "(Ljava/net/URL;)Ljava/net/URLConnection;", + (None,), + ) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("expected NullPointerException for null connection URL"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/NullPointerException")); + } + Ok(()) +} diff --git a/rustjava-runtime/tests/classes/java/net/test_url_class_loader.rs b/rustjava-runtime/tests/classes/java/net/test_url_class_loader.rs index 0d6788fa..f612f0c4 100644 --- a/rustjava-runtime/tests/classes/java/net/test_url_class_loader.rs +++ b/rustjava-runtime/tests/classes/java/net/test_url_class_loader.rs @@ -7,6 +7,37 @@ use zip::{ZipWriter, write::SimpleFileOptions}; use test_utils::test_jvm_filesystem; +#[tokio::test] +async fn test_null_inputs() -> Result<()> { + let jvm = test_jvm_filesystem(Default::default()).await?; + let null_element = jvm.instantiate_array("Ljava/net/URL;", 1).await?; + for urls in [None, Some(null_element)] { + let result = jvm + .new_class("java/net/URLClassLoader", "([Ljava/net/URL;Ljava/lang/ClassLoader;)V", (urls, None)) + .await; + let Err(jvm::JavaError::JavaException(exception)) = result else { + panic!("expected NullPointerException for null URL array or element"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/NullPointerException")); + } + + let urls = jvm.instantiate_array("Ljava/net/URL;", 0).await?; + let loader = jvm + .new_class("java/net/URLClassLoader", "([Ljava/net/URL;Ljava/lang/ClassLoader;)V", (urls, None)) + .await?; + for (method, descriptor) in [ + ("findClass", "(Ljava/lang/String;)Ljava/lang/Class;"), + ("findResource", "(Ljava/lang/String;)Ljava/net/URL;"), + ] { + let result: Result> = jvm.invoke_virtual(&loader, "java/net/URLClassLoader", method, descriptor, (None,)).await; + let Err(jvm::JavaError::JavaException(exception)) = result else { + panic!("expected NullPointerException for {method}(null)"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/NullPointerException")); + } + Ok(()) +} + #[tokio::test] async fn test_jar_loading() -> Result<()> { let entry_name = "\u{c790}\u{b8cc} +%20#.txt"; diff --git a/rustjava-runtime/tests/classes/java/text/test_date_format.rs b/rustjava-runtime/tests/classes/java/text/test_date_format.rs index aaae41ab..65b71f15 100644 --- a/rustjava-runtime/tests/classes/java/text/test_date_format.rs +++ b/rustjava-runtime/tests/classes/java/text/test_date_format.rs @@ -7,6 +7,112 @@ use rustjava_runtime::classes::java::{ use test_utils::test_jvm; +#[tokio::test] +async fn test_date_format_null_calendar_operations() -> Result<()> { + let jvm = test_jvm().await?; + let pattern = JavaLangString::from_rust_string(&jvm, "yyyy-MM-dd").await?; + let format = jvm.new_class("java/text/SimpleDateFormat", "(Ljava/lang/String;)V", (pattern,)).await?; + let _: () = jvm + .invoke_virtual(&format, "java/text/DateFormat", "setCalendar", "(Ljava/util/Calendar;)V", (None,)) + .await?; + + for (method, descriptor, args) in [ + ("getTimeZone", "()Ljava/util/TimeZone;", vec![]), + ("setTimeZone", "(Ljava/util/TimeZone;)V", vec![jvm::JavaValue::Object(None)]), + ("isLenient", "()Z", vec![]), + ("setLenient", "(Z)V", vec![true.into()]), + ( + "format", + "(Ljava/util/Date;)Ljava/lang/String;", + vec![jvm.new_class("java/util/Date", "(J)V", (0i64,)).await?.into()], + ), + ( + "parse", + "(Ljava/lang/String;)Ljava/util/Date;", + vec![JavaLangString::from_rust_string(&jvm, "1970-01-01").await?.into()], + ), + ] { + let result: Result = jvm.invoke_virtual(&format, "java/text/DateFormat", method, descriptor, args).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("{method} with a null calendar must throw NullPointerException"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException"), "{method}"); + } + Ok(()) +} + +#[tokio::test] +async fn test_date_format_nullable_setters() -> Result<()> { + let jvm = test_jvm().await?; + for (setter, getter, class) in [ + ("setCalendar", "getCalendar", "java/util/Calendar"), + ("setNumberFormat", "getNumberFormat", "java/text/NumberFormat"), + ] { + let format = jvm.new_class("java/text/SimpleDateFormat", "()V", ()).await?; + let _: () = jvm + .invoke_virtual(&format, "java/text/DateFormat", setter, &format!("(L{class};)V"), (None,)) + .await?; + let value: ClassInstanceRef = jvm + .invoke_virtual(&format, "java/text/DateFormat", getter, &format!("()L{class};"), ()) + .await?; + assert!(value.is_null()); + let result: Result> = jvm + .invoke_virtual(&format, "java/text/DateFormat", "clone", "()Ljava/lang/Object;", ()) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("clone after {setter}(null) must throw NullPointerException"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + } + for (pattern, source, expected) in [ + ("yyyy", "1970", "npe"), + ("yyyy", " 1970", "npe"), + ("yyyy", " ", "invalid"), + ("yyyy", "\t", "invalid"), + ("'x'yyyy", "x1970", "npe"), + ("'x'yyyy", "wrong", "invalid"), + ("'x'yyyy", "x", "invalid"), + ("'literal'", "literal", "date"), + ("MMMM", "January", "date"), + ("z", "GMT+01:00", "date"), + ] { + let pattern = JavaLangString::from_rust_string(&jvm, pattern).await?; + let format = jvm.new_class("java/text/SimpleDateFormat", "(Ljava/lang/String;)V", (pattern,)).await?; + let _: () = jvm + .invoke_virtual(&format, "java/text/DateFormat", "setNumberFormat", "(Ljava/text/NumberFormat;)V", (None,)) + .await?; + let date = jvm.new_class("java/util/Date", "(J)V", (0i64,)).await?; + let result: Result> = jvm + .invoke_virtual(&format, "java/text/DateFormat", "format", "(Ljava/util/Date;)Ljava/lang/String;", (date,)) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("format with null numberFormat must throw NullPointerException"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + + let source = JavaLangString::from_rust_string(&jvm, source).await?; + let position = jvm.new_class("java/text/ParsePosition", "(I)V", (0,)).await?; + let result: Result> = jvm + .invoke_virtual( + &format, + "java/text/DateFormat", + "parse", + "(Ljava/lang/String;Ljava/text/ParsePosition;)Ljava/util/Date;", + (source, position), + ) + .await; + if expected == "npe" { + let Err(JavaError::JavaException(exception)) = result else { + panic!("numeric parsing with null numberFormat must throw NullPointerException"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + } else { + assert_eq!(result?.is_null(), expected == "invalid"); + } + } + Ok(()) +} + #[tokio::test] async fn test_date_format_factories_use_english_patterns() -> Result<()> { let jvm = test_jvm().await?; diff --git a/rustjava-runtime/tests/classes/java/util/jar/test_jar_file.rs b/rustjava-runtime/tests/classes/java/util/jar/test_jar_file.rs index 7baea2f2..44932ff3 100644 --- a/rustjava-runtime/tests/classes/java/util/jar/test_jar_file.rs +++ b/rustjava-runtime/tests/classes/java/util/jar/test_jar_file.rs @@ -1,6 +1,7 @@ use std::io::{Cursor, Write}; -use jvm::{ClassInstanceRef, Result, runtime::JavaLangString}; +use jvm::{ClassInstanceRef, JavaError, Result, runtime::JavaLangString}; +use rustjava_runtime::classes::java::lang::Object; use rustjava_runtime::classes::java::util::{ jar::{JarEntry, Manifest}, zip::ZipEntry, @@ -9,6 +10,23 @@ use zip::{ZipWriter, write::SimpleFileOptions}; use test_utils::test_jvm_filesystem; +#[tokio::test] +async fn test_jar_null_constructor_arguments() -> Result<()> { + let jvm = test_utils::test_jvm().await?; + for (class, descriptor) in [ + ("java/util/jar/JarEntry", "(Ljava/util/zip/ZipEntry;)V"), + ("java/util/jar/JarFile", "(Ljava/io/File;)V"), + ("java/util/jar/JarFile", "(Ljava/lang/String;)V"), + ] { + let result = jvm.new_class(class, descriptor, (ClassInstanceRef::::new(None),)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("{class}{descriptor} must reject null"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + } + Ok(()) +} + #[tokio::test] async fn test_jar_manifest() -> Result<()> { for (names, expected) in [ diff --git a/rustjava-runtime/tests/classes/java/util/test_gregorian_calendar.rs b/rustjava-runtime/tests/classes/java/util/test_gregorian_calendar.rs index bbba412d..055454c9 100644 --- a/rustjava-runtime/tests/classes/java/util/test_gregorian_calendar.rs +++ b/rustjava-runtime/tests/classes/java/util/test_gregorian_calendar.rs @@ -1,8 +1,78 @@ -use jvm::{ClassInstanceRef, Result, runtime::JavaLangString}; +use jvm::{ClassInstanceRef, JavaError, Result, runtime::JavaLangString}; use rustjava_runtime::classes::java::lang::Object; use test_utils::test_jvm; +#[tokio::test] +async fn test_calendar_nullable_time_zone() -> Result<()> { + let jvm = test_jvm().await?; + let calendar = jvm.new_class("java/util/GregorianCalendar", "()V", ()).await?; + let other = jvm.new_class("java/util/GregorianCalendar", "()V", ()).await?; + for instance in [&calendar, &other] { + let _: () = jvm + .invoke_virtual(instance, "java/util/Calendar", "setTimeInMillis", "(J)V", (0i64,)) + .await?; + } + let zone: ClassInstanceRef = jvm + .invoke_virtual(&calendar, "java/util/Calendar", "getTimeZone", "()Ljava/util/TimeZone;", ()) + .await?; + for instance in [&calendar, &other] { + let _: () = jvm + .invoke_virtual( + instance, + "java/util/Calendar", + "setTimeZone", + "(Ljava/util/TimeZone;)V", + (ClassInstanceRef::::new(None),), + ) + .await?; + let missing: ClassInstanceRef = jvm + .invoke_virtual(instance, "java/util/Calendar", "getTimeZone", "()Ljava/util/TimeZone;", ()) + .await?; + assert!(missing.is_null()); + assert!( + !jvm.invoke_virtual::<_, bool>(&calendar, "java/util/Calendar", "equals", "(Ljava/lang/Object;)Z", (other.clone(),)) + .await? + ); + assert!( + !jvm.invoke_virtual::<_, bool>(&other, "java/util/Calendar", "equals", "(Ljava/lang/Object;)Z", (calendar.clone(),)) + .await? + ); + } + assert!( + jvm.invoke_virtual::<_, bool>(&calendar, "java/util/Calendar", "equals", "(Ljava/lang/Object;)Z", (calendar.clone(),)) + .await? + ); + let hash: Result = jvm.invoke_virtual(&calendar, "java/util/Calendar", "hashCode", "()I", ()).await; + let Err(JavaError::JavaException(exception)) = hash else { + panic!("hashCode with a null time zone must throw"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + let field: Result = jvm.invoke_virtual(&calendar, "java/util/Calendar", "get", "(I)I", (1,)).await; + let Err(JavaError::JavaException(exception)) = field else { + panic!("get with a null time zone must not return cached fields"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + let result: Result<()> = jvm + .invoke_virtual(&calendar, "java/util/Calendar", "setTimeInMillis", "(J)V", (1000i64,)) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("field computation with a null time zone must throw"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + let _: () = jvm + .invoke_virtual(&calendar, "java/util/Calendar", "setTimeZone", "(Ljava/util/TimeZone;)V", (zone,)) + .await?; + let _: () = jvm + .invoke_virtual(&calendar, "java/util/Calendar", "setTimeInMillis", "(J)V", (0i64,)) + .await?; + assert_eq!( + jvm.invoke_virtual::<_, i32>(&calendar, "java/util/Calendar", "get", "(I)I", (1,)).await?, + 1970 + ); + Ok(()) +} + #[tokio::test] async fn test_gregorian_calendar() -> Result<()> { let jvm = test_jvm().await?; diff --git a/rustjava-runtime/tests/classes/java/util/zip/test_zip_file.rs b/rustjava-runtime/tests/classes/java/util/zip/test_zip_file.rs index 07367403..402128d4 100644 --- a/rustjava-runtime/tests/classes/java/util/zip/test_zip_file.rs +++ b/rustjava-runtime/tests/classes/java/util/zip/test_zip_file.rs @@ -1,6 +1,7 @@ use alloc::vec; -use jvm::{Result, runtime::JavaLangString}; +use jvm::{ClassInstanceRef, JavaError, Result, runtime::JavaLangString}; +use rustjava_runtime::classes::java::lang::Object; use test_utils::test_jvm_filesystem; @@ -14,6 +15,25 @@ async fn test_zip_entry() -> Result<()> { let file = jvm.new_class("java/io/File", "(Ljava/lang/String;)V", (name,)).await?; let zip = jvm.new_class("java/util/zip/ZipFile", "(Ljava/io/File;)V", (file,)).await?; + for (method, descriptor) in [ + ("getEntry", "(Ljava/lang/String;)Ljava/util/zip/ZipEntry;"), + ("getInputStream", "(Ljava/util/zip/ZipEntry;)Ljava/io/InputStream;"), + ] { + let result: Result> = jvm + .invoke_virtual( + &zip, + "java/util/zip/ZipFile", + method, + descriptor, + (ClassInstanceRef::::new(None),), + ) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("{method}(null) must throw NullPointerException"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + } + let entry_name = JavaLangString::from_rust_string(&jvm, "test.txt").await?; let entry = jvm .invoke_virtual( @@ -48,3 +68,20 @@ async fn test_zip_entry() -> Result<()> { Ok(()) } + +#[tokio::test] +async fn test_zip_null_constructor_arguments() -> Result<()> { + let jvm = test_utils::test_jvm().await?; + for (class, descriptor) in [ + ("java/util/zip/ZipEntry", "(Ljava/lang/String;)V"), + ("java/util/zip/ZipEntry", "(Ljava/util/zip/ZipEntry;)V"), + ("java/util/zip/ZipFile", "(Ljava/io/File;)V"), + ] { + let result = jvm.new_class(class, descriptor, (ClassInstanceRef::::new(None),)).await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("{class}{descriptor} must reject null"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + } + Ok(()) +} diff --git a/rustjava-runtime/tests/classes/org/rustjava/lang/test_rust_jar_class_loader.rs b/rustjava-runtime/tests/classes/org/rustjava/lang/test_rust_jar_class_loader.rs index 87010de7..8dd052c1 100644 --- a/rustjava-runtime/tests/classes/org/rustjava/lang/test_rust_jar_class_loader.rs +++ b/rustjava-runtime/tests/classes/org/rustjava/lang/test_rust_jar_class_loader.rs @@ -1,6 +1,6 @@ use alloc::vec; -use jvm::{ClassInstanceRef, Result, runtime::JavaLangString}; +use jvm::{ClassInstanceRef, JavaError, Result, runtime::JavaLangString}; use rustjava_runtime::classes::{ java::lang::{Class, ClassLoader}, org::rustjava::lang::RustJarClassLoader, @@ -8,6 +8,48 @@ use rustjava_runtime::classes::{ use test_utils::test_jvm; +#[tokio::test] +async fn test_rustjar_null_inputs() -> Result<()> { + let jvm = test_jvm().await?; + let result = jvm + .new_class( + "org/rustjava/lang/RustJarClassLoader", + "([Ljava/lang/String;Ljava/lang/ClassLoader;)V", + (None, None), + ) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("null classPaths must throw NullPointerException"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + + let class_paths = jvm.instantiate_array("Ljava/lang/String;", 1).await?; + let loader = jvm + .new_class( + "org/rustjava/lang/RustJarClassLoader", + "([Ljava/lang/String;Ljava/lang/ClassLoader;)V", + (class_paths, None), + ) + .await?; + let name = JavaLangString::from_rust_string(&jvm, "java/util/Random").await?; + for name in [None, Some(name)] { + let result: Result> = jvm + .invoke_virtual( + &loader, + "org/rustjava/lang/RustJarClassLoader", + "findClass", + "(Ljava/lang/String;)Ljava/lang/Class;", + (name,), + ) + .await; + let Err(JavaError::JavaException(exception)) = result else { + panic!("null name or classPath must throw NullPointerException"); + }; + assert!(jvm.is_instance(&*exception, "java/lang/NullPointerException")); + } + Ok(()) +} + #[tokio::test] async fn test_find_class_uses_rustjar_runtime_source() -> Result<()> { let jvm = test_jvm().await?; diff --git a/rustjava-runtime/tests/classes/org/rustjava/net/test_file_url_connection.rs b/rustjava-runtime/tests/classes/org/rustjava/net/test_file_url_connection.rs index 2e1af12e..74cb6e0d 100644 --- a/rustjava-runtime/tests/classes/org/rustjava/net/test_file_url_connection.rs +++ b/rustjava-runtime/tests/classes/org/rustjava/net/test_file_url_connection.rs @@ -4,6 +4,21 @@ use jvm::{Result, runtime::JavaLangString}; use test_utils::test_jvm_filesystem; +#[tokio::test] +async fn test_null_url_path() -> Result<()> { + let jvm = test_jvm_filesystem(Default::default()).await?; + for method in ["encode", "decode"] { + let result: Result> = jvm + .invoke_static("org/rustjava/net/URLPath", method, "(Ljava/lang/String;)Ljava/lang/String;", (None,)) + .await; + let Err(jvm::JavaError::JavaException(exception)) = result else { + panic!("expected NullPointerException for {method}(null)"); + }; + assert!(jvm.is_instance(exception.as_ref(), "java/lang/NullPointerException")); + } + Ok(()) +} + #[tokio::test] async fn test_file_url() -> Result<()> { let filesystem = [("test.txt".into(), b"test file content".to_vec())].into_iter().collect();