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
56 changes: 56 additions & 0 deletions ristretto_vm/src/handles/member.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
use ristretto_classloader::Method;
use std::sync::Arc;

/// Represents a handle to a member in the Java Virtual Machine (JVM). This is used to dynamically
/// invoke methods or access fields in a class.
#[derive(Debug)]
pub(crate) struct MemberHandle {
pub(crate) method: Option<Arc<Method>>,
pub(crate) field: Option<usize>,
}

impl From<Arc<Method>> for MemberHandle {
fn from(method: Arc<Method>) -> Self {
MemberHandle {
method: Some(method),
field: None,
}
}
}

impl From<usize> for MemberHandle {
fn from(field: usize) -> Self {
MemberHandle {
method: None,
field: Some(field),
}
}
}

#[cfg(test)]
mod tests {
use super::*;
use crate::Result;

#[tokio::test]
async fn test_member_handle_from_method() -> Result<()> {
let (_vm, thread) = crate::test::thread().await.expect("thread");
let class = thread.class("java.lang.Object").await?;
let method = class.try_get_method("hashCode", "()I")?;
let member_handle: MemberHandle = method.into();
assert_eq!(member_handle.method.expect("method").name(), "hashCode");
assert!(member_handle.field.is_none());
Ok(())
}

#[tokio::test]
async fn test_member_handle_from_field() -> Result<()> {
let (_vm, thread) = crate::test::thread().await.expect("thread");
let class = thread.class("java.lang.Integer").await?;
let field = class.field_offset("serialVersionUID")?;
let method_handle: MemberHandle = field.into();
assert!(method_handle.method.is_none());
assert_eq!(method_handle.field.expect("name"), field);
Ok(())
}
}
2 changes: 2 additions & 0 deletions ristretto_vm/src/handles/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@

mod file;
mod manager;
mod member;
mod thread;

pub(crate) use file::{FileHandle, FileModeFlags};
pub(crate) use manager::HandleManager;
pub(crate) use member::MemberHandle;
pub(crate) use thread::ThreadHandle;
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use crate::Error::InternalError;
use crate::Result;
use crate::intrinsic_methods::java::lang::class::get_class;
use crate::parameters::Parameters;
use crate::thread::Thread;
use crate::{JavaObject, Result};
use async_recursion::async_recursion;
use bitflags::bitflags;
use ristretto_classfile::VersionSpecification::{
Expand Down Expand Up @@ -202,7 +202,6 @@ pub(crate) async fn register_natives(
Ok(None)
}

#[expect(clippy::too_many_lines)]
pub(crate) async fn resolve(
thread: Arc<Thread>,
member_self: Value,
Expand All @@ -221,18 +220,62 @@ pub(crate) async fn resolve(
let class_object = class_object.as_object_ref()?;
class_object.value("name")?.as_string()?
};
let class = thread.class(class_name.clone()).await?;
let _reference_kind = get_reference_kind(flags)?;
let class = thread.class(class_name).await?;
let member_name_flags = MemberNameFlags::from_bits_truncate(flags);

// Handle methods/constructors
if member_name_flags.contains(MemberNameFlags::IS_METHOD)
|| member_name_flags.contains(MemberNameFlags::IS_CONSTRUCTOR)
{
let method_type = {
let member_self = member_self.as_object_ref()?;
member_self.value("type")?
};
resolve_method(
&thread,
member_self,
&caller,
lookup_mode_flags,
speculative_resolve,
&name,
flags,
&class,
)
.await
} else if member_name_flags.contains(MemberNameFlags::IS_FIELD) {
resolve_field(
&thread,
member_self,
caller,
lookup_mode_flags,
speculative_resolve,
name,
flags,
&class,
)
.await
} else {
Err(InternalError(format!(
"Unsupported member name flag: {member_name_flags:?}"
)))
}
}

/// Resolves a method in the given class, checking access permissions and returning the member self
/// if successful.
#[expect(clippy::too_many_arguments)]
async fn resolve_method(
thread: &Thread,
member_self: Value,
caller: &Option<Arc<Class>>,
lookup_mode_flags: &LookupModeFlags,
speculative_resolve: bool,
name: &Value,
flags: i32,
class: &Arc<Class>,
) -> Result<Option<Value>> {
let _reference_kind = get_reference_kind(flags)?;
let method_type = {
let member_self = member_self.as_object_ref()?;
member_self.value("type")?
};

let (parameter_descriptors, return_descriptor) = {
let method_type = method_type.as_object_ref()?;
let parameter_types = method_type.value("ptypes")?;
let parameters: Vec<Value> = parameter_types.try_into()?;
Expand All @@ -247,82 +290,108 @@ pub(crate) async fn resolve(
let return_type = return_type.as_object_ref()?;
let return_class_name = return_type.value("name")?.as_string()?;
let return_descriptor = Class::convert_to_descriptor(&return_class_name);
(parameter_descriptors, return_descriptor)
};

let method_name = name.as_string()?;
let method_descriptor = format!("({}){return_descriptor}", parameter_descriptors.concat());
let method = match class_name.as_str() {
"java.lang.invoke.DelegatingMethodHandle$Holder"
| "java.lang.invoke.DirectMethodHandle$Holder"
| "java.lang.invoke.Invokers$Holder" => {
resolve_holder_methods(class.clone(), &method_name, &method_descriptor)?
}
_ => class.try_get_method(method_name.clone(), method_descriptor.clone())?,
let method_name = name.as_string()?;
let method_descriptor = format!("({}){return_descriptor}", parameter_descriptors.concat());
let method = match class.name() {
"java.lang.invoke.DelegatingMethodHandle$Holder"
| "java.lang.invoke.DirectMethodHandle$Holder"
| "java.lang.invoke.Invokers$Holder" => {
resolve_holder_methods(class.clone(), &method_name, &method_descriptor)?
}
_ => class.try_get_method(&method_name, &method_descriptor)?,
};

// Access control enforcement
let method_access_flags = method.access_flags();
if !check_method_access(caller, class, *method_access_flags, *lookup_mode_flags)? {
return if speculative_resolve {
// If speculative, return None (fail silently)
Ok(None)
} else {
Err(IllegalAccessError(format!(
"member is {}: {}.{method_name}{method_descriptor}",
if method_access_flags.contains(MethodAccessFlags::PRIVATE) {
"private"
} else {
"inaccessible"
},
class.name(),
))
.into())
};
}

// Access control enforcement
let method_access_flags = method.access_flags();
if !check_method_access(caller, &class, *method_access_flags, *lookup_mode_flags)? {
return if speculative_resolve {
// If speculative, return None (fail silently)
Ok(None)
} else {
Err(IllegalAccessError(format!(
"member is {}: {}.{}{}",
if method_access_flags.contains(MethodAccessFlags::PRIVATE) {
"private"
} else {
"inaccessible"
},
class_name,
method_name,
method_descriptor,
))
.into())
};
}
let modifiers = i32::from(method_access_flags.bits());
let flags = flags | modifiers;
{
let vm = thread.vm()?;
let member_handles = vm.member_handles();
let method_signature =
format!("{}.{}{}", class.name(), method.name(), method.descriptor(),);
member_handles
.insert(method_signature, method.into())
.await?;
let _vmindex = method_descriptor.to_object(thread).await?;
let mut member_self = member_self.as_object_mut()?;
member_self.set_value("flags", Value::from(flags))?;
// member_self.set_value("vmindex", vmindex)?;
}
Ok(Some(member_self))
}

let modifiers = i32::from(method_access_flags.bits());
let flags = flags | modifiers;
{
let mut member_self = member_self.as_object_mut()?;
member_self.set_value("flags", Value::from(flags))?;
}
Ok(Some(member_self))
}
// Handle fields (for both normal field and VarHandle)
else if member_name_flags.contains(MemberNameFlags::IS_FIELD) {
let field_name = name.as_string()?;
let field = class.declared_field(&field_name)?;
let field_access_flags = field.access_flags();
if !check_field_access(caller, &class, *field_access_flags, *lookup_mode_flags)? {
return if speculative_resolve {
Ok(None)
} else {
Err(IllegalAccessError(format!(
"member is {}: {}.{}",
if field_access_flags.contains(FieldAccessFlags::PRIVATE) {
"private"
} else {
"inaccessible"
},
class_name,
field_name,
))
.into())
};
}
let modifiers = i32::from(field_access_flags.bits());
let flags = flags | modifiers;
{
let mut member_self = member_self.as_object_mut()?;
member_self.set_value("flags", Value::from(flags))?;
}
Ok(Some(member_self))
} else {
Err(InternalError(format!(
"Unsupported member name flag: {member_name_flags:?}"
)))
/// Resolves a field in the given class, checking access permissions and returning the member self
/// if successful.
#[expect(clippy::too_many_arguments)]
async fn resolve_field(
thread: &Thread,
member_self: Value,
caller: Option<Arc<Class>>,
lookup_mode_flags: &LookupModeFlags,
speculative_resolve: bool,
name: Value,
flags: i32,
class: &Arc<Class>,
) -> Result<Option<Value>> {
let _reference_kind = get_reference_kind(flags)?;
let field_name = name.as_string()?;
let field = class.declared_field(&field_name)?;
let field_access_flags = field.access_flags();
if !check_field_access(caller, class, *field_access_flags, *lookup_mode_flags)? {
return if speculative_resolve {
Ok(None)
} else {
Err(IllegalAccessError(format!(
"member is {}: {}.{}",
if field_access_flags.contains(FieldAccessFlags::PRIVATE) {
"private"
} else {
"inaccessible"
},
class.name(),
field_name,
))
.into())
};
}
let modifiers = i32::from(field_access_flags.bits());
let flags = flags | modifiers;
{
let vm = thread.vm()?;
let member_handles = vm.member_handles();
let field_offset = class.field_offset(&field_name)?;
let field_signature = format!("{}.{field_name}", class.name(),);
member_handles
.insert(field_signature.clone(), field_offset.into())
.await?;
let _vmindex = field_signature.to_object(thread).await?;
let mut member_self = member_self.as_object_mut()?;
member_self.set_value("flags", Value::from(flags))?;
// member_self.set_value("vmindex", vmindex)?;
}
Ok(Some(member_self))
}

/// Extracts the reference kind from the flags of a member name.
Expand All @@ -340,9 +409,8 @@ fn get_reference_kind(flags: i32) -> Result<ReferenceKind> {
/// # References
///
/// - [JLS §6.6 Access Control](https://docs.oracle.com/javase/specs/jls/se24/html/jls-6.html#jls-6.6)
#[expect(clippy::needless_pass_by_value)]
pub fn check_method_access(
caller: Option<Arc<Class>>,
caller: &Option<Arc<Class>>,
declaring: &Arc<Class>,
method_access_flags: MethodAccessFlags,
lookup_mode_flags: LookupModeFlags,
Expand All @@ -356,7 +424,7 @@ pub fn check_method_access(
return Ok(true);
}

let Some(ref caller) = caller else {
let Some(caller) = caller else {
return Ok(false);
};

Expand Down
9 changes: 8 additions & 1 deletion ristretto_vm/src/vm.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::Error::InternalError;
use crate::call_site_cache::CallSiteCache;
use crate::handles::{FileHandle, HandleManager, ThreadHandle};
use crate::handles::{FileHandle, HandleManager, MemberHandle, ThreadHandle};
use crate::intrinsic_methods::MethodRegistry;
use crate::java_object::JavaObject;
use crate::rust_value::RustValue;
Expand Down Expand Up @@ -44,6 +44,7 @@ pub struct VM {
next_thread_id: AtomicU64,
thread_handles: HandleManager<u64, ThreadHandle>,
file_handles: HandleManager<String, FileHandle>,
member_handles: HandleManager<String, MemberHandle>,
string_pool: StringPool,
call_site_cache: CallSiteCache,
}
Expand Down Expand Up @@ -159,6 +160,7 @@ impl VM {
next_thread_id: AtomicU64::new(1),
thread_handles: HandleManager::new(),
file_handles: HandleManager::new(),
member_handles: HandleManager::new(),
string_pool: StringPool::new(),
call_site_cache: CallSiteCache::new(),
});
Expand Down Expand Up @@ -273,6 +275,11 @@ impl VM {
&self.file_handles
}

/// Get the VM member handles used for dynamic invocation
pub(crate) fn member_handles(&self) -> &HandleManager<String, MemberHandle> {
&self.member_handles
}

/// Initialize the VM
///
/// # Errors
Expand Down
Loading