use crate::Error::InternalError;
use crate::JavaError::IllegalAccessError;
use crate::Result;
use dashmap::DashMap;
use ristretto_classfile::{FieldType, JavaStr};
use ristretto_classloader::{Class, Method, POLYMORPHIC_METHODS};
use std::sync::Arc;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct MethodRefKey {
pub caller_class: String,
pub cp_index: u16,
}
impl MethodRefKey {
#[must_use]
pub fn new(caller_class: String, cp_index: u16) -> Self {
Self {
caller_class,
cp_index,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum InvokeKind {
Static,
Special,
Virtual,
Interface,
}
#[derive(Debug, Clone)]
pub struct ResolvedMethodRef {
pub declaring_class: Arc<Class>,
pub method: Arc<Method>,
pub invoke_kind: InvokeKind,
pub method_name: String,
pub method_descriptor: String,
pub is_polymorphic: bool,
pub param_count: usize,
pub has_return_type: bool,
}
impl ResolvedMethodRef {
#[must_use]
pub fn new(
declaring_class: Arc<Class>,
method: Arc<Method>,
invoke_kind: InvokeKind,
method_descriptor: String,
) -> Self {
let method_name = method.name().to_string();
let is_polymorphic = POLYMORPHIC_METHODS
.get(&(declaring_class.name(), method.name()))
.is_some();
let (param_count, has_return_type) = if is_polymorphic {
let d = JavaStr::cow_from_str(&method_descriptor);
match FieldType::parse_method_descriptor(&d).ok() {
Some((params, return_type)) => (params.len(), return_type.is_some()),
None => (method.parameters().len(), method.return_type().is_some()),
}
} else {
(method.parameters().len(), method.return_type().is_some())
};
Self {
declaring_class,
method,
invoke_kind,
method_name,
method_descriptor,
is_polymorphic,
param_count,
has_return_type,
}
}
}
#[derive(Debug, Clone)]
pub enum MethodRefState {
Resolving,
Resolved(ResolvedMethodRef),
Failed(MethodRefError),
}
#[derive(Debug, Clone)]
pub struct MethodRefError {
pub kind: MethodRefErrorKind,
pub message: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MethodRefErrorKind {
ModuleNotReadable,
PackageNotExported,
MemberNotAccessible,
NoSuchMethod,
IncompatibleClassChange,
InternalError,
}
impl MethodRefError {
#[must_use]
pub fn new(kind: MethodRefErrorKind, message: String) -> Self {
Self { kind, message }
}
pub fn to_vm_error(&self) -> crate::Error {
use crate::JavaError::{IncompatibleClassChangeError, NoSuchMethodError};
match self.kind {
MethodRefErrorKind::ModuleNotReadable
| MethodRefErrorKind::PackageNotExported
| MethodRefErrorKind::MemberNotAccessible => {
IllegalAccessError(self.message.clone()).into()
}
MethodRefErrorKind::NoSuchMethod => NoSuchMethodError(self.message.clone()).into(),
MethodRefErrorKind::IncompatibleClassChange => {
IncompatibleClassChangeError(self.message.clone()).into()
}
MethodRefErrorKind::InternalError => InternalError(self.message.clone()),
}
}
}
#[derive(Debug)]
pub struct MethodRefCache {
states: DashMap<MethodRefKey, MethodRefState>,
}
impl MethodRefCache {
#[must_use]
pub fn new() -> Self {
Self {
states: DashMap::new(),
}
}
#[must_use]
pub fn get(&self, key: &MethodRefKey) -> Option<Result<ResolvedMethodRef>> {
self.states.get(key).and_then(|state| match &*state {
MethodRefState::Resolving => None, MethodRefState::Resolved(resolved) => Some(Ok(resolved.clone())),
MethodRefState::Failed(error) => Some(Err(error.to_vm_error())),
})
}
pub fn mark_resolving(&self, key: MethodRefKey) -> bool {
use dashmap::mapref::entry::Entry;
match self.states.entry(key) {
Entry::Occupied(entry) => !matches!(entry.get(), MethodRefState::Resolving),
Entry::Vacant(entry) => {
entry.insert(MethodRefState::Resolving);
true
}
}
}
pub fn store_resolved(&self, key: MethodRefKey, resolved: ResolvedMethodRef) {
self.states.insert(key, MethodRefState::Resolved(resolved));
}
pub fn store_failed(&self, key: MethodRefKey, error: MethodRefError) {
self.states.insert(key, MethodRefState::Failed(error));
}
pub fn remove(&self, key: &MethodRefKey) {
self.states.remove(key);
}
pub fn clear(&self) {
self.states.clear();
}
#[must_use]
pub fn len(&self) -> usize {
self.states.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.states.is_empty()
}
pub async fn resolve_with_cache<F, Fut>(
&self,
key: MethodRefKey,
resolve_fn: F,
) -> Result<ResolvedMethodRef>
where
F: FnOnce() -> Fut,
Fut: Future<Output = Result<ResolvedMethodRef>>,
{
if let Some(state) = self.states.get(&key) {
return match &*state {
MethodRefState::Resolving => Err(InternalError(format!(
"Recursive method resolution detected for class '{}' at index {}",
key.caller_class, key.cp_index
))),
MethodRefState::Resolved(cached) => Ok(cached.clone()),
MethodRefState::Failed(error) => Err(error.to_vm_error()),
};
}
self.states.insert(key.clone(), MethodRefState::Resolving);
let result = resolve_fn().await;
match &result {
Ok(method_ref) => {
self.states
.insert(key, MethodRefState::Resolved(method_ref.clone()));
}
Err(error) => {
let cached_error =
MethodRefError::new(MethodRefErrorKind::InternalError, error.to_string());
self.states
.insert(key, MethodRefState::Failed(cached_error));
}
}
result
}
}
impl Default for MethodRefCache {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_method_ref_key_equality() {
let key1 = MethodRefKey::new("com/example/Test".to_string(), 10);
let key2 = MethodRefKey::new("com/example/Test".to_string(), 10);
let key3 = MethodRefKey::new("com/example/Test".to_string(), 11);
assert_eq!(key1, key2);
assert_ne!(key1, key3);
}
#[test]
fn test_method_ref_cache_new() {
let cache = MethodRefCache::new();
assert!(cache.is_empty());
assert_eq!(cache.len(), 0);
}
#[test]
fn test_method_ref_cache_get_nonexistent() {
let cache = MethodRefCache::new();
let key = MethodRefKey::new("Test".to_string(), 1);
assert!(cache.get(&key).is_none());
}
#[test]
fn test_method_ref_error_kinds() {
let error = MethodRefError::new(
MethodRefErrorKind::ModuleNotReadable,
"test error".to_string(),
);
let vm_error = error.to_vm_error();
assert!(format!("{vm_error:?}").contains("IllegalAccessError"));
let error = MethodRefError::new(
MethodRefErrorKind::NoSuchMethod,
"method not found".to_string(),
);
let vm_error = error.to_vm_error();
assert!(format!("{vm_error:?}").contains("NoSuchMethodError"));
}
#[test]
fn test_invoke_kind() {
assert_eq!(InvokeKind::Static, InvokeKind::Static);
assert_ne!(InvokeKind::Static, InvokeKind::Virtual);
}
#[test]
fn test_mark_resolving() {
let cache = MethodRefCache::new();
let key = MethodRefKey::new("Test".to_string(), 1);
assert!(cache.mark_resolving(key.clone()));
assert_eq!(cache.len(), 1);
}
#[test]
fn test_store_failed() {
let cache = MethodRefCache::new();
let key = MethodRefKey::new("Test".to_string(), 1);
let error = MethodRefError::new(
MethodRefErrorKind::NoSuchMethod,
"Method not found".to_string(),
);
cache.store_failed(key.clone(), error);
let result = cache.get(&key);
assert!(result.is_some());
assert!(result.unwrap().is_err());
}
#[test]
fn test_remove() {
let cache = MethodRefCache::new();
let key = MethodRefKey::new("Test".to_string(), 1);
let error = MethodRefError::new(
MethodRefErrorKind::NoSuchMethod,
"Method not found".to_string(),
);
cache.store_failed(key.clone(), error);
assert_eq!(cache.len(), 1);
cache.remove(&key);
assert!(cache.is_empty());
}
#[test]
fn test_clear() {
let cache = MethodRefCache::new();
let key1 = MethodRefKey::new("Test1".to_string(), 1);
let key2 = MethodRefKey::new("Test2".to_string(), 2);
let error = MethodRefError::new(
MethodRefErrorKind::NoSuchMethod,
"Method not found".to_string(),
);
cache.store_failed(key1, error.clone());
cache.store_failed(key2, error);
assert_eq!(cache.len(), 2);
cache.clear();
assert!(cache.is_empty());
}
#[tokio::test]
async fn test_resolve_with_cache_caches_failure() {
let cache = MethodRefCache::new();
let key = MethodRefKey::new("Test".to_string(), 1);
let result = cache
.resolve_with_cache(key.clone(), || async {
Err(crate::JavaError::NoSuchMethodError("test".to_string()).into())
})
.await;
assert!(result.is_err());
let result = cache
.resolve_with_cache(key, || async {
panic!("Resolver should not be called for cached failure")
})
.await;
assert!(result.is_err());
}
}