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
21 changes: 20 additions & 1 deletion jvm/src/garbage_collector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,21 @@ pub fn determine_garbage(
find_reachable_objects(jvm, object, &mut reachable_objects)?;
}

// java.lang.ref: a weak reference is cleared once its referent is no longer strongly reachable
if let Some(reference_class) = classes.get("java/lang/ref/Reference") {
let referent = reference_class.definition.field("referent", "Ljava/lang/Object;", false).unwrap();
for object in all_class_instances
.iter()
.filter(|object| reachable_objects.contains(&object.identity()) && jvm.is_instance(&***object, "java/lang/ref/Reference"))
{
if let JavaValue::Object(Some(value)) = object.get_field(&*referent)?
&& !reachable_objects.contains(&value.identity())
{
object.clone().put_field(&*referent, JavaValue::Object(None))?;
}
}
}

Ok(all_class_instances
.iter()
.filter(|object| !reachable_objects.contains(&object.identity()))
Expand Down Expand Up @@ -112,7 +127,11 @@ fn find_reachable_objects(jvm: &Jvm, object: &Box<dyn ClassInstance>, reachable_
}

fn find_all_fields(jvm: &Jvm, class_definition: &dyn ClassDefinition) -> Vec<Box<dyn Field>> {
let result = class_definition.fields();
let mut result = class_definition.fields();
if class_definition.name() == "java/lang/ref/Reference" {
// weak: the referent must not be kept reachable through the reference
result.retain(|field| field.name() != "referent");
}
let super_class_name = class_definition.super_class_name();

if let Some(x) = super_class_name {
Expand Down
2 changes: 2 additions & 0 deletions rustjava-runtime/src/classes/java/lang.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ mod unsupported_operation_exception;
mod verify_error;
mod virtual_machine_error;

pub mod r#ref;

pub use self::{
abstract_method_error::AbstractMethodError, abstract_string_builder::AbstractStringBuilder, appendable::Appendable,
arithmetic_exception::ArithmeticException, array_index_out_of_bounds_exception::ArrayIndexOutOfBoundsException,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use alloc::vec;
use alloc::{format, vec};

use jvm::{ClassInstanceRef, Jvm, Result};
use jvm::{ClassInstanceRef, Jvm, Result, runtime::JavaLangString};
use jvm_class_proto::JavaMethodProto;
use jvm_types::{ClassAccessFlags, MethodAccessFlags};

Expand All @@ -18,6 +18,7 @@ impl ArrayIndexOutOfBoundsException {
methods: vec![
JavaMethodProto::new("<init>", "()V", Self::init, MethodAccessFlags::PUBLIC),
JavaMethodProto::new("<init>", "(Ljava/lang/String;)V", Self::init_with_message, MethodAccessFlags::PUBLIC),
JavaMethodProto::new("<init>", "(I)V", Self::init_with_index, MethodAccessFlags::PUBLIC),
],
fields: vec![],
access_flags: ClassAccessFlags::PUBLIC,
Expand Down Expand Up @@ -47,4 +48,21 @@ impl ArrayIndexOutOfBoundsException {

Ok(())
}

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

let message = JavaLangString::from_rust_string(jvm, &format!("Array index out of range: {index}")).await?;
let _: () = jvm
.invoke_special(
&this,
"java/lang/IndexOutOfBoundsException",
"<init>",
"(Ljava/lang/String;)V",
(message,),
)
.await?;

Ok(())
}
}
4 changes: 4 additions & 0 deletions rustjava-runtime/src/classes/java/lang/ref.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
mod reference;
mod weak_reference;

pub use {reference::Reference, weak_reference::WeakReference};
57 changes: 57 additions & 0 deletions rustjava-runtime/src/classes/java/lang/ref/reference.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
use alloc::vec;

use jvm::{ClassInstanceRef, Jvm, Result};
use jvm_class_proto::{JavaFieldProto, JavaMethodProto};
use jvm_types::{ClassAccessFlags, FieldAccessFlags, MethodAccessFlags};

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

// abstract class java.lang.ref.Reference
pub struct Reference;

impl Reference {
pub fn as_proto() -> RuntimeClassProto {
RuntimeClassProto {
name: "java/lang/ref/Reference",
parent_class: Some("java/lang/Object"),
interfaces: vec![],
methods: vec![
JavaMethodProto::new("<init>", "(Ljava/lang/Object;)V", Self::init, MethodAccessFlags::empty()),
JavaMethodProto::new("get", "()Ljava/lang/Object;", Self::get, MethodAccessFlags::PUBLIC),
JavaMethodProto::new("clear", "()V", Self::clear, MethodAccessFlags::PUBLIC),
],
// the garbage collector does not trace this field and clears it once the referent is otherwise unreachable
fields: vec![JavaFieldProto::new("referent", "Ljava/lang/Object;", FieldAccessFlags::PRIVATE)],
access_flags: ClassAccessFlags::PUBLIC | ClassAccessFlags::ABSTRACT,
}
}

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

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

Ok(())
}

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

jvm.get_field(&this, "java/lang/ref/Reference", "referent", "Ljava/lang/Object;").await
}

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

jvm.put_field(
&mut this,
"java/lang/ref/Reference",
"referent",
"Ljava/lang/Object;",
ClassInstanceRef::<Object>::new(None),
)
.await
}
}
35 changes: 35 additions & 0 deletions rustjava-runtime/src/classes/java/lang/ref/weak_reference.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
use alloc::vec;

use jvm::{ClassInstanceRef, Jvm, Result};
use jvm_class_proto::JavaMethodProto;
use jvm_types::{ClassAccessFlags, MethodAccessFlags};

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

// class java.lang.ref.WeakReference
pub struct WeakReference;

impl WeakReference {
pub fn as_proto() -> RuntimeClassProto {
RuntimeClassProto {
name: "java/lang/ref/WeakReference",
parent_class: Some("java/lang/ref/Reference"),
interfaces: vec![],
methods: vec![JavaMethodProto::new(
"<init>",
"(Ljava/lang/Object;)V",
Self::init,
MethodAccessFlags::PUBLIC,
)],
fields: vec![],
access_flags: ClassAccessFlags::PUBLIC,
}
}

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

jvm.invoke_special(&this, "java/lang/ref/Reference", "<init>", "(Ljava/lang/Object;)V", (referent,))
.await
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use alloc::vec;
use alloc::{format, vec};

use jvm::{ClassInstanceRef, Jvm, Result};
use jvm::{ClassInstanceRef, Jvm, Result, runtime::JavaLangString};
use jvm_class_proto::JavaMethodProto;
use jvm_types::{ClassAccessFlags, MethodAccessFlags};

Expand All @@ -18,6 +18,7 @@ impl StringIndexOutOfBoundsException {
methods: vec![
JavaMethodProto::new("<init>", "()V", Self::init, MethodAccessFlags::PUBLIC),
JavaMethodProto::new("<init>", "(Ljava/lang/String;)V", Self::init_with_message, MethodAccessFlags::PUBLIC),
JavaMethodProto::new("<init>", "(I)V", Self::init_with_index, MethodAccessFlags::PUBLIC),
],
fields: vec![],
access_flags: ClassAccessFlags::PUBLIC,
Expand Down Expand Up @@ -49,4 +50,21 @@ impl StringIndexOutOfBoundsException {

Ok(())
}

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

let message = JavaLangString::from_rust_string(jvm, &format!("String index out of range: {index}")).await?;
let _: () = jvm
.invoke_special(
&this,
"java/lang/IndexOutOfBoundsException",
"<init>",
"(Ljava/lang/String;)V",
(message,),
)
.await?;

Ok(())
}
}
32 changes: 32 additions & 0 deletions rustjava-runtime/src/classes/java/util/timer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@ impl Timer {
Self::schedule_at_fixed_rate,
MethodAccessFlags::PUBLIC,
),
JavaMethodProto::new(
"scheduleAtFixedRate",
"(Ljava/util/TimerTask;Ljava/util/Date;J)V",
Self::schedule_date_at_fixed_rate,
MethodAccessFlags::PUBLIC,
),
JavaMethodProto::new("cancel", "()V", Self::cancel, MethodAccessFlags::PUBLIC),
],
fields: vec![JavaFieldProto::new(
Expand Down Expand Up @@ -206,6 +212,32 @@ impl Timer {
Self::sched(jvm, this, task, time, period).await
}

async fn schedule_date_at_fixed_rate(
jvm: &Jvm,
_: &mut RuntimeContext,
this: ClassInstanceRef<Self>,
task: ClassInstanceRef<TimerTask>,
date: ClassInstanceRef<Date>,
period: i64,
) -> Result<()> {
tracing::debug!("java.util.Timer::scheduleAtFixedRate({this:?}, {task:?}, {date:?}, {period:?})");

if task.is_null() {
return Err(jvm.exception("java/lang/NullPointerException", "task").await);
}
if date.is_null() {
return Err(jvm.exception("java/lang/NullPointerException", "time").await);
}
if period <= 0 {
return Err(jvm.exception("java/lang/IllegalArgumentException", "non-positive period").await);
}
let time: i64 = jvm.invoke_virtual(&date, "java/util/Date", "getTime", "()J", ()).await?;
if time < 0 {
return Err(jvm.exception("java/lang/IllegalArgumentException", "illegal execution time").await);
}
Self::sched(jvm, this, task, time, period).await
}

async fn sched(jvm: &Jvm, this: ClassInstanceRef<Self>, mut task: ClassInstanceRef<TimerTask>, time: i64, period: i64) -> Result<()> {
let period = if !(-(i64::MAX >> 1)..=(i64::MAX >> 1)).contains(&period) {
period >> 1
Expand Down
2 changes: 2 additions & 0 deletions rustjava-runtime/src/loader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,8 @@ pub fn get_runtime_class_proto(name: &str) -> Option<RuntimeClassProto> {
crate::classes::java::lang::UnsatisfiedLinkError::as_proto(),
crate::classes::java::lang::VerifyError::as_proto(),
crate::classes::java::lang::VirtualMachineError::as_proto(),
crate::classes::java::lang::r#ref::Reference::as_proto(),
crate::classes::java::lang::r#ref::WeakReference::as_proto(),
crate::classes::java::net::JarURLConnection::as_proto(),
crate::classes::java::net::MalformedURLException::as_proto(),
crate::classes::java::net::UnknownServiceException::as_proto(),
Expand Down
19 changes: 19 additions & 0 deletions rustjava-runtime/tests/classes/java/lang/test_cldc11_exceptions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,3 +149,22 @@ async fn test_runtime_api_exception_and_error_message_constructors() -> Result<(

Ok(())
}

#[tokio::test]
async fn test_index_exception_index_constructors() -> Result<()> {
let jvm = test_jvm().await?;

for (class_name, index, expected) in [
("java/lang/ArrayIndexOutOfBoundsException", 5, "Array index out of range: 5"),
("java/lang/StringIndexOutOfBoundsException", -3, "String index out of range: -3"),
] {
let instance = jvm.new_class(class_name, "(I)V", (index,)).await?;
let message: ClassInstanceRef<String> = jvm
.invoke_virtual(&instance, class_name, "getMessage", "()Ljava/lang/String;", ())
.await?;

assert_eq!(JavaLangString::to_rust_string(&jvm, &message).await?, expected);
}

Ok(())
}
47 changes: 33 additions & 14 deletions rustjava-runtime/tests/classes/java/util/test_timer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ async fn timer_01_to_05_registers_jdk_shaped_api_and_state() -> Result<()> {
("schedule", "(Ljava/util/TimerTask;JJ)V"),
("schedule", "(Ljava/util/TimerTask;Ljava/util/Date;J)V"),
("scheduleAtFixedRate", "(Ljava/util/TimerTask;JJ)V"),
("scheduleAtFixedRate", "(Ljava/util/TimerTask;Ljava/util/Date;J)V"),
("cancel", "()V"),
] {
let method = timer
Expand Down Expand Up @@ -390,6 +391,22 @@ async fn timer_normalizes_long_max_period_and_rejects_first_recurrence_overflow(
(-i64::MAX) >> 1
);
assert_eq!(jvm.get_field::<i64>(&fixed_rate, "TestTimerTask", "period", "J").await?, i64::MAX >> 1);
let dated_fixed_rate: ClassInstanceRef<TestTimerTask> = jvm.new_class("TestTimerTask", "()V", ()).await?.into();
let date = jvm.new_class("java/util/Date", "(J)V", (2_000i64,)).await?;
let _: () = jvm
.invoke_virtual(
&timer,
&timer.class_definition().name(),
"scheduleAtFixedRate",
"(Ljava/util/TimerTask;Ljava/util/Date;J)V",
(dated_fixed_rate.clone(), date, 25i64),
)
.await?;
assert_eq!(
jvm.get_field::<i64>(&dated_fixed_rate, "TestTimerTask", "nextExecutionTime", "J").await?,
2_000
);
assert_eq!(jvm.get_field::<i64>(&dated_fixed_rate, "TestTimerTask", "period", "J").await?, 25);
let _: () = jvm.invoke_virtual(&timer, &timer.class_definition().name(), "cancel", "()V", ()).await?;
await_spawn(next_spawn(&runtime).await, "large-period timer worker").await?;

Expand All @@ -412,21 +429,23 @@ async fn timer_normalizes_long_max_period_and_rejects_first_recurrence_overflow(
assert_eq!(jvm.get_field::<i32>(&task, "TestTimerTask", "state", "I").await?, 0);
assert_eq!(jvm.get_field::<i64>(&task, "TestTimerTask", "period", "J").await?, 0);
}
let date = jvm.new_class("java/util/Date", "(J)V", (i64::MAX - 50,)).await?;
let dated: ClassInstanceRef<TestTimerTask> = jvm.new_class("TestTimerTask", "()V", ()).await?.into();
assert_exception(
jvm.invoke_virtual::<_, ()>(
&timer,
&timer.class_definition().name(),
"schedule",
"(Ljava/util/TimerTask;Ljava/util/Date;J)V",
(dated.clone(), date, 60i64),
for method in ["schedule", "scheduleAtFixedRate"] {
let date = jvm.new_class("java/util/Date", "(J)V", (i64::MAX - 50,)).await?;
let dated: ClassInstanceRef<TestTimerTask> = jvm.new_class("TestTimerTask", "()V", ()).await?.into();
assert_exception(
jvm.invoke_virtual::<_, ()>(
&timer,
&timer.class_definition().name(),
method,
"(Ljava/util/TimerTask;Ljava/util/Date;J)V",
(dated.clone(), date, 60i64),
)
.await,
"java/lang/IllegalArgumentException",
)
.await,
"java/lang/IllegalArgumentException",
)
.await;
assert_eq!(jvm.get_field::<i32>(&dated, "TestTimerTask", "state", "I").await?, 0);
.await;
assert_eq!(jvm.get_field::<i32>(&dated, "TestTimerTask", "state", "I").await?, 0);
}
let _: () = jvm.invoke_virtual(&timer, &timer.class_definition().name(), "cancel", "()V", ()).await?;
await_spawn(next_spawn(&runtime).await, "near-boundary validation timer worker").await?;
Ok(())
Expand Down
Binary file added test-data/WeakReferenceSemantics$Node.class
Binary file not shown.
Binary file added test-data/WeakReferenceSemantics.class
Binary file not shown.
8 changes: 8 additions & 0 deletions test-data/WeakReferenceSemantics.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
held: true
held by field: true
collected: true
chain head: true
chain tail: true
empty: true
cleared: true
held after clear: true
Loading
Loading