Skip to content
Merged
17 changes: 9 additions & 8 deletions java_runtime/src/classes/java/lang.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ mod cloneable;
mod comparable;
mod error;
mod exception;
mod exception_in_initializer_error;
mod illegal_argument_exception;
mod incompatible_class_change_error;
mod index_out_of_bounds_exception;
Expand Down Expand Up @@ -40,12 +41,12 @@ pub use self::{
abstract_method_error::AbstractMethodError, arithmetic_exception::ArithmeticException,
array_index_out_of_bounds_exception::ArrayIndexOutOfBoundsException, class::Class, class_cast_exception::ClassCastException,
class_loader::ClassLoader, clone_not_supported_exception::CloneNotSupportedException, cloneable::Cloneable, comparable::Comparable, error::Error,
exception::Exception, 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,
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,
};
2 changes: 1 addition & 1 deletion java_runtime/src/classes/java/lang/class.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ impl Class {
tracing::debug!("java.lang.Class::getName({:?})", &this);

let rust_class = JavaLangClass::to_rust_class(jvm, &this).await?;
let result = JavaLangString::from_rust_string(jvm, &rust_class.name()).await?;
let result = JavaLangString::from_rust_string(jvm, &rust_class.name().replace('/', ".")).await?;

Ok(result.into())
}
Expand Down
44 changes: 43 additions & 1 deletion java_runtime/src/classes/java/lang/exception.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ use alloc::vec;
use java_class_proto::JavaMethodProto;
use jvm::{ClassInstanceRef, Jvm, Result};

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

// class java.lang.Exception
pub struct Exception;
Expand All @@ -17,6 +20,13 @@ impl Exception {
methods: vec![
JavaMethodProto::new("<init>", "()V", Self::init, Default::default()),
JavaMethodProto::new("<init>", "(Ljava/lang/String;)V", Self::init_with_message, Default::default()),
JavaMethodProto::new("<init>", "(Ljava/lang/Throwable;)V", Self::init_with_cause, Default::default()),
JavaMethodProto::new(
"<init>",
"(Ljava/lang/String;Ljava/lang/Throwable;)V",
Self::init_with_message_and_cause,
Default::default(),
),
],
fields: vec![],
access_flags: Default::default(),
Expand All @@ -40,4 +50,36 @@ impl Exception {

Ok(())
}

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

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

Ok(())
}

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

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

Ok(())
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
use alloc::vec;

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

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

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

impl ExceptionInInitializerError {
pub fn as_proto() -> RuntimeClassProto {
RuntimeClassProto {
name: "java/lang/ExceptionInInitializerError",
parent_class: Some("java/lang/LinkageError"),
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()),
JavaMethodProto::new("<init>", "(Ljava/lang/Throwable;)V", Self::init_with_cause, Default::default()),
JavaMethodProto::new("getException", "()Ljava/lang/Throwable;", Self::get_exception, Default::default()),
],
fields: vec![],
access_flags: Default::default(),
}
}

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

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

Ok(())
}

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

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

Ok(())
}

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

// unlike Throwable(Throwable), this keeps detailMessage null so toString is just the class name
let _: () = jvm.invoke_special(&this, "java/lang/LinkageError", "<init>", "()V", ()).await?;

jvm.put_field(&mut this, "cause", "Ljava/lang/Throwable;", cause).await?;

Ok(())
}

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

jvm.get_field(&this, "cause", "Ljava/lang/Throwable;").await
}
}
44 changes: 43 additions & 1 deletion java_runtime/src/classes/java/lang/runtime_exception.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ use alloc::vec;
use java_class_proto::JavaMethodProto;
use jvm::{ClassInstanceRef, Jvm, Result};

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

// class java.lang.RuntimeException
pub struct RuntimeException;
Expand All @@ -17,6 +20,13 @@ impl RuntimeException {
methods: vec![
JavaMethodProto::new("<init>", "()V", Self::init, Default::default()),
JavaMethodProto::new("<init>", "(Ljava/lang/String;)V", Self::init_with_message, Default::default()),
JavaMethodProto::new("<init>", "(Ljava/lang/Throwable;)V", Self::init_with_cause, Default::default()),
JavaMethodProto::new(
"<init>",
"(Ljava/lang/String;Ljava/lang/Throwable;)V",
Self::init_with_message_and_cause,
Default::default(),
),
],
fields: vec![],
access_flags: Default::default(),
Expand All @@ -40,4 +50,36 @@ impl RuntimeException {

Ok(())
}

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

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

Ok(())
}

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

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

Ok(())
}
}
121 changes: 105 additions & 16 deletions java_runtime/src/classes/java/lang/throwable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,20 @@ impl Throwable {
methods: vec![
JavaMethodProto::new("<init>", "()V", Self::init, Default::default()),
JavaMethodProto::new("<init>", "(Ljava/lang/String;)V", Self::init_with_message, Default::default()),
JavaMethodProto::new("<init>", "(Ljava/lang/Throwable;)V", Self::init_with_cause, Default::default()),
JavaMethodProto::new(
"<init>",
"(Ljava/lang/String;Ljava/lang/Throwable;)V",
Self::init_with_message_and_cause,
Default::default(),
),
JavaMethodProto::new("getCause", "()Ljava/lang/Throwable;", Self::get_cause, Default::default()),
JavaMethodProto::new(
"initCause",
"(Ljava/lang/Throwable;)Ljava/lang/Throwable;",
Self::init_cause,
Default::default(),
),
JavaMethodProto::new("toString", "()Ljava/lang/String;", Self::to_string, Default::default()),
JavaMethodProto::new(
"fillInStackTrace",
Expand All @@ -46,6 +60,7 @@ impl Throwable {
],
fields: vec![
JavaFieldProto::new("detailMessage", "Ljava/lang/String;", Default::default()),
JavaFieldProto::new("cause", "Ljava/lang/Throwable;", Default::default()),
JavaFieldProto::new("stackTrace", "[Ljava/lang/String;", Default::default()),
],
access_flags: Default::default(),
Expand Down Expand Up @@ -74,6 +89,62 @@ impl Throwable {
Ok(())
}

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

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

let message: ClassInstanceRef<String> = if cause.is_null() {
None.into()
} else {
jvm.invoke_virtual(&cause, "toString", "()Ljava/lang/String;", ()).await?
};
jvm.put_field(&mut this, "detailMessage", "Ljava/lang/String;", message).await?;
jvm.put_field(&mut this, "cause", "Ljava/lang/Throwable;", cause).await?;

let _: ClassInstanceRef<Self> = jvm.invoke_virtual(&this, "fillInStackTrace", "()Ljava/lang/Throwable;", ()).await?;

Ok(())
}

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

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

jvm.put_field(&mut this, "detailMessage", "Ljava/lang/String;", message).await?;
jvm.put_field(&mut this, "cause", "Ljava/lang/Throwable;", cause).await?;

let _: ClassInstanceRef<Self> = jvm.invoke_virtual(&this, "fillInStackTrace", "()Ljava/lang/Throwable;", ()).await?;

Ok(())
}

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

jvm.get_field(&this, "cause", "Ljava/lang/Throwable;").await
}

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

jvm.put_field(&mut this, "cause", "Ljava/lang/Throwable;", cause).await?;

Ok(this)
}

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

Expand Down Expand Up @@ -150,23 +221,41 @@ impl Throwable {
}

async fn do_print_stack_trace(jvm: &Jvm, this: ClassInstanceRef<Self>, stream_or_writer: Box<dyn ClassInstance>) -> Result<()> {
let stack_trace: ClassInstanceRef<Array<ClassInstanceRef<String>>> = jvm.get_field(&this, "stackTrace", "[Ljava/lang/String;").await?;

// TODO we can call println(Ljava/lang/Object;)V
let string: ClassInstanceRef<String> = jvm.invoke_virtual(&this, "toString", "()Ljava/lang/String;", ()).await?;
let _: () = jvm
.invoke_virtual(&stream_or_writer, "println", "(Ljava/lang/String;)V", (string,))
.await?;

if !stack_trace.is_null() {
let length = jvm.array_length(&stack_trace).await?;
let lines: Vec<ClassInstanceRef<String>> = jvm.load_array(&stack_trace, 0, length).await?;
for line_ref in lines {
let line = JavaLangString::to_rust_string(jvm, &line_ref).await?;
let line = format!("\tat {line}");
let line = JavaLangString::from_rust_string(jvm, &line).await?;
let _: () = jvm.invoke_virtual(&stream_or_writer, "println", "(Ljava/lang/String;)V", (line,)).await?;
let mut current: ClassInstanceRef<Self> = this;
let mut header: Option<&str> = None;

// a malformed initCause could create a cycle, so cap the depth
for _ in 0..32 {
let string: ClassInstanceRef<String> = jvm.invoke_virtual(&current, "toString", "()Ljava/lang/String;", ()).await?;
let prefix: ClassInstanceRef<String> = match header {
Some(x) => {
let string = JavaLangString::to_rust_string(jvm, &string).await?;
JavaLangString::from_rust_string(jvm, &format!("{x}{string}")).await?.into()
}
None => string,
};
let _: () = jvm
.invoke_virtual(&stream_or_writer, "println", "(Ljava/lang/String;)V", (prefix,))
.await?;

let stack_trace: ClassInstanceRef<Array<ClassInstanceRef<String>>> = jvm.get_field(&current, "stackTrace", "[Ljava/lang/String;").await?;
if !stack_trace.is_null() {
let length = jvm.array_length(&stack_trace).await?;
let lines: Vec<ClassInstanceRef<String>> = jvm.load_array(&stack_trace, 0, length).await?;
for line_ref in lines {
let line = JavaLangString::to_rust_string(jvm, &line_ref).await?;
let line = format!("\tat {line}");
let line = JavaLangString::from_rust_string(jvm, &line).await?;
let _: () = jvm.invoke_virtual(&stream_or_writer, "println", "(Ljava/lang/String;)V", (line,)).await?;
}
}

let cause: ClassInstanceRef<Self> = jvm.invoke_virtual(&current, "getCause", "()Ljava/lang/Throwable;", ()).await?;
if cause.is_null() {
break;
}
current = cause;
header = Some("Caused by: ");
}

Ok(())
Expand Down
1 change: 1 addition & 0 deletions java_runtime/src/loader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ pub fn get_runtime_class_proto(name: &str) -> Option<RuntimeClassProto> {
crate::classes::java::lang::Comparable::as_proto(),
crate::classes::java::lang::Error::as_proto(),
crate::classes::java::lang::Exception::as_proto(),
crate::classes::java::lang::ExceptionInInitializerError::as_proto(),
crate::classes::java::lang::IllegalArgumentException::as_proto(),
crate::classes::java::lang::InstantiationError::as_proto(),
crate::classes::java::lang::IncompatibleClassChangeError::as_proto(),
Expand Down
Loading
Loading