Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 4 additions & 15 deletions java_runtime/src/classes/java/io/file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,12 +59,7 @@ impl File {
let path = jvm.invoke_virtual(&this, "getPath", "()Ljava/lang/String;", ()).await?;
let path = JavaLangString::to_rust_string(jvm, &path).await?;

let stat = context.metadata(&path).await;
if stat.is_err() {
return Ok(false);
}

Ok(stat.unwrap().r#type == FileType::Directory)
Ok(context.metadata(&path).await.is_ok_and(|x| x.r#type == FileType::Directory))
}

async fn is_file(jvm: &Jvm, context: &mut RuntimeContext, this: ClassInstanceRef<Self>) -> Result<bool> {
Expand All @@ -73,12 +68,7 @@ impl File {
let path = jvm.invoke_virtual(&this, "getPath", "()Ljava/lang/String;", ()).await?;
let path = JavaLangString::to_rust_string(jvm, &path).await?;

let stat = context.metadata(&path).await;
if stat.is_err() {
return Ok(false);
}

Ok(stat.unwrap().r#type == FileType::File)
Ok(context.metadata(&path).await.is_ok_and(|x| x.r#type == FileType::File))
}

async fn delete(jvm: &Jvm, context: &mut RuntimeContext, this: ClassInstanceRef<Self>) -> Result<bool> {
Expand All @@ -96,8 +86,7 @@ impl File {
let path = jvm.invoke_virtual(&this, "getPath", "()Ljava/lang/String;", ()).await?;
let path = JavaLangString::to_rust_string(jvm, &path).await?;

let stat = context.metadata(&path).await.unwrap();

Ok(stat.size as _)
// File.length() is 0 when the file does not exist
Ok(context.metadata(&path).await.map_or(0, |x| x.size as _))
}
}
16 changes: 12 additions & 4 deletions java_runtime/src/classes/java/io/file_input_stream.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,12 @@ impl FileInputStream {
let rust_file = FileDescriptor::file(jvm, context, fd).await?;

// TODO get os buffer size
let stat = rust_file.metadata().await.unwrap();
let tell = rust_file.tell().await.unwrap();
let Ok(stat) = rust_file.metadata().await else {
return Err(jvm.exception("java/io/IOException", "I/O error").await);
};
let Ok(tell) = rust_file.tell().await else {
return Err(jvm.exception("java/io/IOException", "I/O error").await);
};

let available = stat.size - tell;

Expand All @@ -101,7 +105,9 @@ impl FileInputStream {
let mut rust_file = FileDescriptor::file(jvm, context, fd).await?;

let mut rust_buf = vec![0; length as _];
let read = rust_file.read(&mut rust_buf).await.unwrap();
let Ok(read) = rust_file.read(&mut rust_buf).await else {
return Err(jvm.exception("java/io/IOException", "I/O error").await);
};
if read == 0 {
return Ok(-1);
}
Expand All @@ -118,7 +124,9 @@ impl FileInputStream {
let mut rust_file = FileDescriptor::file(jvm, context, fd).await?;

let mut buf = [0; 1];
let read = rust_file.read(&mut buf).await.unwrap();
let Ok(read) = rust_file.read(&mut buf).await else {
return Err(jvm.exception("java/io/IOException", "I/O error").await);
};
if read == 0 {
return Ok(-1);
}
Expand Down
18 changes: 13 additions & 5 deletions java_runtime/src/classes/java/io/file_output_stream.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,12 @@ impl FileOutputStream {
let path = jvm.invoke_virtual(&file, "getPath", "()Ljava/lang/String;", ()).await?;
let path = JavaLangString::to_rust_string(jvm, &path).await?;

let fd = context.open(&path, true).await.unwrap();
let fd = FileDescriptor::from_fd(jvm, fd).await?;
let fd = context.open(&path, true).await;
if fd.is_err() {
return Err(jvm.exception("java/io/FileNotFoundException", "File not found").await);
}

let fd = FileDescriptor::from_fd(jvm, fd.unwrap()).await?;

let _: () = jvm
.invoke_special(&this, "java/io/FileOutputStream", "<init>", "(Ljava/io/FileDescriptor;)V", (fd,))
Expand Down Expand Up @@ -81,9 +85,11 @@ impl FileOutputStream {
let mut file = FileDescriptor::file(jvm, context, fd).await?;

let mut buf = vec![0; length as _];
jvm.array_raw_buffer(&buffer).await?.read(offset as _, &mut buf).unwrap();
jvm.array_raw_buffer(&buffer).await?.read(offset as _, &mut buf)?;

file.write(cast_slice(&buf)).await.unwrap();
if file.write(cast_slice(&buf)).await.is_err() {
return Err(jvm.exception("java/io/IOException", "I/O error").await);
}

Ok(())
}
Expand All @@ -94,7 +100,9 @@ impl FileOutputStream {
let fd = jvm.get_field(&this, "fd", "Ljava/io/FileDescriptor;").await?;
let mut file = FileDescriptor::file(jvm, context, fd).await?;

file.write(&[byte as u8]).await.unwrap();
if file.write(&[byte as u8]).await.is_err() {
return Err(jvm.exception("java/io/IOException", "I/O error").await);
}

Ok(())
}
Expand Down
2 changes: 1 addition & 1 deletion java_runtime/src/classes/java/io/input_stream_reader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ impl InputStreamReader {

let read_buf_size: i32 = jvm.get_field(&this, "readBufSize", "I").await?;
let mut read_buf_data = vec![0; read_buf_size as _];
jvm.array_raw_buffer(&read_buf).await?.read(0, &mut read_buf_data).unwrap();
jvm.array_raw_buffer(&read_buf).await?.read(0, &mut read_buf_data)?;

let charset_ref = jvm.get_field(&this, "charset", "Ljava/lang/String;").await?;
let charset = JavaLangString::to_rust_string(jvm, &charset_ref).await?;
Expand Down
3 changes: 2 additions & 1 deletion java_runtime/src/classes/java/io/print_stream.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,8 @@ impl PrintStream {
async fn println_char(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef<Self>, char: JavaChar) -> Result<()> {
tracing::debug!("java.io.PrintStream::println({this:?}, {char:?})");

let char = char::from_u32(char as _).unwrap();
// an unpaired surrogate is not a valid char; the JDK charset encoder replaces it with '?'
let char = char::from_u32(char as _).unwrap_or('?');

let java_string = JavaLangString::from_rust_string(jvm, &char.to_string()).await?;

Expand Down
27 changes: 20 additions & 7 deletions java_runtime/src/classes/java/io/random_access_file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,9 @@ impl RandomAccessFile {
let mut rust_file = FileDescriptor::file(jvm, context, fd).await?;

let mut rust_buf = vec![0; length as usize];
let read = rust_file.read(&mut rust_buf).await.unwrap();
let Ok(read) = rust_file.read(&mut rust_buf).await else {
return Err(jvm.exception("java/io/IOException", "I/O error").await);
};

jvm.array_raw_buffer_mut(&mut buf).await?.write(offset as _, &rust_buf)?;

Expand Down Expand Up @@ -144,8 +146,10 @@ impl RandomAccessFile {
let mut rust_file = FileDescriptor::file(jvm, context, fd).await?;

let mut rust_buf = vec![0; length as usize];
jvm.array_raw_buffer(&buf).await?.read(offset as _, &mut rust_buf).unwrap();
rust_file.write(&cast_vec(rust_buf)).await.unwrap();
jvm.array_raw_buffer(&buf).await?.read(offset as _, &mut rust_buf)?;
if rust_file.write(&cast_vec(rust_buf)).await.is_err() {
return Err(jvm.exception("java/io/IOException", "I/O error").await);
}

Ok(())
}
Expand All @@ -156,7 +160,9 @@ impl RandomAccessFile {
let fd = jvm.get_field(&this, "fd", "Ljava/io/FileDescriptor;").await?;
let mut rust_file = FileDescriptor::file(jvm, context, fd).await?;

rust_file.seek(pos as _).await.unwrap();
if rust_file.seek(pos as _).await.is_err() {
return Err(jvm.exception("java/io/IOException", "I/O error").await);
}

Ok(())
}
Expand All @@ -167,7 +173,9 @@ impl RandomAccessFile {
let fd = jvm.get_field(&this, "fd", "Ljava/io/FileDescriptor;").await?;
let mut rust_file = FileDescriptor::file(jvm, context, fd).await?;

rust_file.set_len(new_length as _).await.unwrap();
if rust_file.set_len(new_length as _).await.is_err() {
return Err(jvm.exception("java/io/IOException", "I/O error").await);
}

Ok(())
}
Expand All @@ -178,7 +186,10 @@ impl RandomAccessFile {
let fd = jvm.get_field(&this, "fd", "Ljava/io/FileDescriptor;").await?;
let rust_file = FileDescriptor::file(jvm, context, fd).await?;

let len = rust_file.metadata().await.unwrap().size;
let Ok(metadata) = rust_file.metadata().await else {
return Err(jvm.exception("java/io/IOException", "I/O error").await);
};
let len = metadata.size;

Ok(len as i64)
}
Expand All @@ -189,7 +200,9 @@ impl RandomAccessFile {
let fd = jvm.get_field(&this, "fd", "Ljava/io/FileDescriptor;").await?;
let rust_file = FileDescriptor::file(jvm, context, fd).await?;

let pos = rust_file.tell().await.unwrap();
let Ok(pos) = rust_file.tell().await else {
return Err(jvm.exception("java/io/IOException", "I/O error").await);
};

Ok(pos as i64)
}
Expand Down
21 changes: 11 additions & 10 deletions java_runtime/src/classes/java/lang.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ mod array_store_exception;
mod class;
mod class_cast_exception;
mod class_loader;
mod class_not_found_exception;
mod clone_not_supported_exception;
mod cloneable;
mod comparable;
Expand Down Expand Up @@ -41,14 +42,14 @@ mod unsupported_operation_exception;
pub use self::{
abstract_method_error::AbstractMethodError, arithmetic_exception::ArithmeticException,
array_index_out_of_bounds_exception::ArrayIndexOutOfBoundsException, array_store_exception::ArrayStoreException, class::Class,
class_cast_exception::ClassCastException, class_loader::ClassLoader, clone_not_supported_exception::CloneNotSupportedException,
cloneable::Cloneable, comparable::Comparable, error::Error, exception::Exception, exception_in_initializer_error::ExceptionInInitializerError,
illegal_argument_exception::IllegalArgumentException, incompatible_class_change_error::IncompatibleClassChangeError,
index_out_of_bounds_exception::IndexOutOfBoundsException, instantiation_error::InstantiationError, integer::Integer,
interrupted_exception::InterruptedException, linkage_error::LinkageError, math::Math, negative_array_size_exception::NegativeArraySizeException,
no_class_def_found_error::NoClassDefFoundError, no_such_field_error::NoSuchFieldError, no_such_method_error::NoSuchMethodError,
null_pointer_exception::NullPointerException, number_format_exception::NumberFormatException, object::Object, runnable::Runnable,
runtime::Runtime, runtime_exception::RuntimeException, security_exception::SecurityException, string::String, string_buffer::StringBuffer,
string_index_out_of_bounds_exception::StringIndexOutOfBoundsException, system::System, thread::Thread, throwable::Throwable,
unsupported_operation_exception::UnsupportedOperationException,
class_cast_exception::ClassCastException, class_loader::ClassLoader, class_not_found_exception::ClassNotFoundException,
clone_not_supported_exception::CloneNotSupportedException, cloneable::Cloneable, comparable::Comparable, error::Error, exception::Exception,
exception_in_initializer_error::ExceptionInInitializerError, illegal_argument_exception::IllegalArgumentException,
incompatible_class_change_error::IncompatibleClassChangeError, index_out_of_bounds_exception::IndexOutOfBoundsException,
instantiation_error::InstantiationError, integer::Integer, interrupted_exception::InterruptedException, linkage_error::LinkageError, math::Math,
negative_array_size_exception::NegativeArraySizeException, no_class_def_found_error::NoClassDefFoundError, no_such_field_error::NoSuchFieldError,
no_such_method_error::NoSuchMethodError, null_pointer_exception::NullPointerException, number_format_exception::NumberFormatException,
object::Object, runnable::Runnable, runtime::Runtime, runtime_exception::RuntimeException, security_exception::SecurityException, string::String,
string_buffer::StringBuffer, string_index_out_of_bounds_exception::StringIndexOutOfBoundsException, system::System, thread::Thread,
throwable::Throwable, unsupported_operation_exception::UnsupportedOperationException,
};
6 changes: 4 additions & 2 deletions java_runtime/src/classes/java/lang/class.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,10 @@ impl Class {

let rust_name = JavaLangString::to_rust_string(jvm, &name).await?;
let qualified_name = rust_name.replace('.', "/");
let class = jvm.get_class(&qualified_name).unwrap().java_class();

Ok(class.into())
match jvm.resolve_class(&qualified_name).await {
Ok(class) => Ok(class.java_class().into()),
Err(_) => Err(jvm.exception("java/lang/ClassNotFoundException", &rust_name).await),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve linkage errors from Class.forName

When the requested class file is present but resolving one of its superclasses or interfaces fails, resolve_class propagates that Java error (for example NoClassDefFoundError from jvm/src/jvm.rs when a dependency is missing). This blanket Err(_) converts those linkage failures into ClassNotFoundException, so Java code can incorrectly catch them as CNFE instead of seeing the linkage error that Class.forName should propagate. Only the actual “requested class not found” case should be remapped here; other resolve_class errors should be returned unchanged.

Useful? React with 👍 / 👎.

}
}
}
43 changes: 43 additions & 0 deletions java_runtime/src/classes/java/lang/class_not_found_exception.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
use alloc::vec;

use java_class_proto::JavaMethodProto;
use jvm::{ClassInstanceRef, Jvm, Result};

use crate::{RuntimeClassProto, RuntimeContext, classes::java::lang::String};

// class java.lang.ClassNotFoundException
pub struct ClassNotFoundException;

impl ClassNotFoundException {
pub fn as_proto() -> RuntimeClassProto {
RuntimeClassProto {
name: "java/lang/ClassNotFoundException",
parent_class: Some("java/lang/Exception"),
interfaces: vec![],
methods: vec![
JavaMethodProto::new("<init>", "()V", Self::init, Default::default()),
JavaMethodProto::new("<init>", "(Ljava/lang/String;)V", Self::init_with_message, Default::default()),
],
fields: vec![],
access_flags: Default::default(),
}
}

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

let _: () = jvm.invoke_special(&this, "java/lang/Exception", "<init>", "()V", ()).await?;

Ok(())
}

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

let _: () = jvm
.invoke_special(&this, "java/lang/Exception", "<init>", "(Ljava/lang/String;)V", (message,))
.await?;

Ok(())
}
}
6 changes: 4 additions & 2 deletions java_runtime/src/classes/java/lang/string.rs
Original file line number Diff line number Diff line change
Expand Up @@ -379,9 +379,11 @@ impl String {
async fn value_of_char(jvm: &Jvm, _: &mut RuntimeContext, value: JavaChar) -> Result<ClassInstanceRef<Self>> {
tracing::debug!("java.lang.String::valueOf({value})");

let string = RustString::from_utf16(&[value]).unwrap();
// build through [C so an unpaired surrogate is preserved
let mut chars = jvm.instantiate_array("C", 1).await?;
jvm.store_array(&mut chars, 0, [value]).await?;

Ok(JavaLangString::from_rust_string(jvm, &string).await?.into())
Ok(jvm.new_class("java/lang/String", "([C)V", (chars,)).await?.into())
}

async fn value_of_integer(jvm: &Jvm, _: &mut RuntimeContext, value: i32) -> Result<ClassInstanceRef<Self>> {
Expand Down
18 changes: 7 additions & 11 deletions java_runtime/src/classes/java/lang/string_buffer.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
use alloc::{
string::{String as RustString, ToString},
vec,
vec::Vec,
};
use alloc::{string::ToString, vec, vec::Vec};

use java_class_proto::{JavaFieldProto, JavaMethodProto};
use jvm::{Array, ClassInstanceRef, JavaChar, Jvm, Result, runtime::JavaLangString};
Expand Down Expand Up @@ -162,9 +158,7 @@ impl StringBuffer {
async fn append_character(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef<Self>, value: u16) -> Result<ClassInstanceRef<Self>> {
tracing::debug!("java.lang.StringBuffer::append({this:?}, {value:?})");

let value = RustString::from_utf16(&[value]).unwrap();

Self::append(jvm, &mut this, &value).await?;
Self::append_utf16(jvm, &mut this, vec![value]).await?;

Ok(this)
}
Expand All @@ -180,9 +174,8 @@ impl StringBuffer {
tracing::debug!("java.lang.StringBuffer::append({this:?}, {array:?}, {offset:?}, {length:?})");

let value: Vec<JavaChar> = jvm.load_array(&array, offset as _, length as _).await?;
let string = RustString::from_utf16(&value).unwrap();

Self::append(jvm, &mut this, &string).await?;
Self::append_utf16(jvm, &mut this, value).await?;

Ok(this)
}
Expand Down Expand Up @@ -237,9 +230,12 @@ impl StringBuffer {
}

async fn append(jvm: &Jvm, this: &mut ClassInstanceRef<Self>, string: &str) -> Result<()> {
Self::append_utf16(jvm, this, string.encode_utf16().collect()).await
}

async fn append_utf16(jvm: &Jvm, this: &mut ClassInstanceRef<Self>, value_to_add: Vec<JavaChar>) -> Result<()> {
let current_count: i32 = jvm.get_field(this, "count", "I").await?;

let value_to_add = string.encode_utf16().collect::<Vec<_>>();
let count_to_add = value_to_add.len() as i32;

StringBuffer::ensure_capacity(jvm, this, (current_count + count_to_add) as _).await?;
Expand Down
3 changes: 2 additions & 1 deletion java_runtime/src/classes/java/util/zip.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
mod zip_entry;
mod zip_exception;
mod zip_file;
mod zip_file_entries;

pub use {zip_entry::ZipEntry, zip_file::ZipFile, zip_file_entries::ZipFileEntries};
pub use {zip_entry::ZipEntry, zip_exception::ZipException, zip_file::ZipFile, zip_file_entries::ZipFileEntries};
Loading