Stack Traces

This commit is contained in:
james 2025-12-10 18:08:35 +10:30
parent 707b961903
commit bf259da7c9
No known key found for this signature in database
GPG Key ID: E1FFBA228F4CAD87
21 changed files with 753 additions and 248 deletions

View File

@ -15,6 +15,7 @@ libloading = { workspace = true }
libffi = { workspace = true }
jni = { workspace = true }
itertools = { workspace = true }
colored = "3.0.0"
[build-dependencies]
bindgen = "0.72.1"

View File

@ -3,7 +3,7 @@ use crate::class_file::{ClassFile, Constant, ConstantPoolEntry};
use deku::DekuContainerRead;
use deku_derive::DekuRead;
use log::trace;
use std::fmt::Display;
use std::fmt::{Display, Formatter};
use std::ops::Deref;
#[derive(Clone, PartialEq, Debug, DekuRead)]
@ -60,6 +60,22 @@ pub enum ArrayType {
T_LONG,
}
impl Display for ArrayType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let str = match self {
ArrayType::T_BOOLEAN => "[Z",
ArrayType::T_CHAR => "[C",
ArrayType::T_FLOAT => "[F",
ArrayType::T_DOUBLE => "[D",
ArrayType::T_BYTE => "[B",
ArrayType::T_SHORT => "[S",
ArrayType::T_INT => "[I",
ArrayType::T_LONG => "[J",
};
write!(f, "{}", str)
}
}
// impl TryFrom<u8> for Ops {
// type Error = ();
//

View File

@ -79,7 +79,7 @@ impl Bimage {
slashes.replace("/", ".")
}
pub fn get_class(&mut self, module: &str, class: &str) -> Option<Vec<u8>> {
pub fn get_class(&mut self, module: &str, class: &str) -> Result<Vec<u8>, String> {
// trace!("Modules{:#?}", self.modules);
let path = Self::resolve_path(module, class);
@ -87,7 +87,7 @@ impl Bimage {
.read_file(&path)
.map_err(|e| {
log::trace!("Class not found {}", path);
e.to_string()
})
.ok()
}
}

View File

@ -3,11 +3,12 @@ use crate::class_file::{
ClassFile, ClassFlags, ConstantPoolEntry, FieldData, FieldInfo, FieldRef, MethodData,
MethodInfo, MethodRef,
};
use crate::{FieldType, MethodDescriptor, VmError};
use crate::{FieldType, MethodDescriptor};
use log::trace;
use std::hash::{Hash, Hasher};
use std::sync::{Arc, Mutex, OnceLock, OnceState};
use std::thread::ThreadId;
use crate::error::VmError;
/// JVM Spec 5.5: Initialization states for a class
#[derive(Debug, Clone, PartialEq)]
@ -37,6 +38,7 @@ pub struct RuntimeClass {
pub super_classes: Vec<Arc<RuntimeClass>>,
pub super_interfaces: Vec<Arc<RuntimeClass>>,
pub component_type: Option<Arc<RuntimeClass>>,
pub source_file: Option<String>,
}
impl Hash for RuntimeClass {
fn hash<H: Hasher>(&self, state: &mut H) {
@ -98,8 +100,23 @@ impl RuntimeClass {
}
pub fn is_assignable_into(&self, into: Arc<RuntimeClass>) -> bool {
self.eq(&*into)
|| self.super_classes.contains(&into)
|| self.super_interfaces.contains(&into)
if self.eq(&*into) || self.super_classes.contains(&into) || self.super_interfaces.contains(&into) {
return true;
}
// Array covariance: both must be arrays, then check component types
if let (Some(self_comp), Some(into_comp)) = (&self.component_type, &into.component_type) {
// Primitive components must match exactly
if self_comp.is_primitive_class() {
return self_comp.eq(&*into_comp);
}
// Reference components: recursive covariance
return self_comp.is_assignable_into(into_comp.clone());
}
false
}
fn is_primitive_class(&self) -> bool {
matches!(self.this_class.as_str(), "byte"|"char"|"double"|"float"|"int"|"long"|"short"|"boolean")
}
}

View File

@ -1,4 +1,4 @@
use crate::attributes::{Attribute, AttributeInfo, CodeAttribute};
use crate::attributes::{Attribute, AttributeInfo, CodeAttribute, LineNumberTableEntry};
use crate::class_file::constant_pool::{ConstantPoolError, ConstantPoolExt, ConstantPoolOwned};
use crate::instructions::Ops;
use crate::value::Value;
@ -657,6 +657,7 @@ pub struct MethodData {
pub desc: MethodDescriptor,
pub code: Option<CodeAttribute>,
pub flags: MethodFlags,
pub line_number_table: Option<Vec<LineNumberTableEntry>>,
// pub exceptions: Option<_>,
// pub visible_annotations: Option<_>,
// pub invisible_annotations: Option<_>,

View File

@ -5,16 +5,13 @@ use crate::class_file::{
ConstantClassInfo, ConstantDynamicInfo, ConstantFieldrefInfo, ConstantInterfaceMethodrefInfo,
ConstantInvokeDynamicInfo, ConstantMethodHandleInfo, ConstantMethodTypeInfo,
ConstantMethodrefInfo, ConstantModuleInfo, ConstantNameAndTypeInfo, ConstantPackageInfo,
ConstantPoolEntry, ConstantStringInfo, ConstantUtf8Info, DescParseError, FieldInfo, FieldRef,
MethodInfo, MethodRef,
ConstantPoolEntry, ConstantStringInfo, ConstantUtf8Info, DescParseError, FieldInfo, FieldRef
, MethodRef,
};
use crate::{pool_get_impl, FieldType, MethodDescriptor, VmError};
use crate::{pool_get_impl, FieldType, MethodDescriptor};
use deku::DekuContainerRead;
use log::trace;
use std::fmt::{Display, Formatter};
use std::ops::Deref;
use std::str::FromStr;
use std::sync::Arc;
use crate::error::VmError;
pub type ConstantPoolSlice = [ConstantPoolEntry];
pub type ConstantPoolOwned = Vec<ConstantPoolEntry>;
@ -100,6 +97,16 @@ pub trait ConstantPoolExt: ConstantPoolGet {
Ok(MethodRef { class, name, desc })
}
fn resolve_interface_method_ref(&self, index: u16) -> Result<MethodRef, ConstantPoolError> {
let mr = self.get_interface_method_ref(index)?;
let class = self.resolve_class_name(mr.class_index)?;
let name_and_type = self.get_name_and_type_info(mr.name_and_type_index)?;
let name = self.get_string(name_and_type.name_index)?;
let desc = self.get_string(name_and_type.descriptor_index)?;
let desc = MethodDescriptor::parse(&desc)?;
Ok(MethodRef { class, name, desc })
}
/*// (name, desc)
fn resolve_method_info(&self, method: &MethodInfo) -> Result<MethodData, ConstantPoolError> {
let desc = self.get_string(method.descriptor_index)?;

View File

@ -12,6 +12,7 @@ use std::fs::File;
use std::io::Read;
use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex, OnceLock};
use crate::error::VmError;
pub type LoaderRef = Arc<Mutex<ClassLoader>>;
@ -141,7 +142,7 @@ impl ClassLoader {
&mut self,
class_name: &str,
loader: LoaderId,
) -> Result<Arc<RuntimeClass>, String> {
) -> Result<Arc<RuntimeClass>, VmError> {
if let Some(class) = self.classes.get(&(class_name.to_string(), loader)) {
return Ok(class.clone());
}
@ -164,14 +165,15 @@ impl ClassLoader {
.expect("invalid L descriptor");
self.get_or_load(class_name, None)
}
None => Err("empty component descriptor".to_string()),
_ => Err(format!("invalid component descriptor: {}", component_name)),
};
let component = self.get_or_load(component_name, None)?;
None => Err(VmError::LoaderError("empty component descriptor".to_string())),
_ => Err(VmError::LoaderError(format!("invalid component descriptor: {}", component_name))),
}?;
// let component = self.get_or_load(component_name, None)?;
let arr_class = self.create_array_class(
component,
);
return Ok(Arc::new(arr_class))
self.needs_init.push(arr_class.clone());
return Ok(arr_class)
}
let class = self.load_class(class_name, loader)?;
self.needs_init.push(class.clone());
@ -182,14 +184,19 @@ impl ClassLoader {
self.classes.clone()
}*/
fn load_class(&mut self, what: &str, loader: LoaderId) -> Result<Arc<RuntimeClass>, String> {
fn load_class(&mut self, what: &str, loader: LoaderId) -> Result<Arc<RuntimeClass>, VmError> {
let (module, class_fqn) = ("", what);
let bytes = self
.bimage
.get_class(module, class_fqn)
.unwrap_or_else(|| Self::load_class_from_disk(what));
.get_class(module, class_fqn).or_else(|e| Self::load_class_from_disk(what)).map_err(|e1| {
let classes = self.classes.iter().map(|x| {
x.this_class.clone()
}).collect::<Vec<_>>();
// println!("{:#?}", classes);
VmError::LoaderError(format!("failed to find class: {:?}\n{:#?}", what, classes))
})?;
let (_, cf) = ClassFile::from_bytes((bytes.as_ref(), 0))
.map_err(|e| format!("failed to parse class file: {}", e))?;
.map_err(|e| VmError::DekuError(e))?;
let runtime = self.runtime_class(cf);
let arced = Arc::new(runtime);
let option = self
@ -201,7 +208,7 @@ impl ClassLoader {
Ok(arced)
}
fn load_class_from_disk(what: &str) -> Vec<u8> {
fn load_class_from_disk(what: &str) -> Result<Vec<u8>, String> {
let class_path = std::env::args()
.nth(1)
.unwrap_or("./data".to_string())
@ -209,10 +216,10 @@ impl ClassLoader {
let path = format!("{class_path}/{what}.class");
log::info!("Loading class from path: {}", path);
let mut class_file = File::open(path).unwrap();
let mut class_file = File::open(path).map_err(|e| {e.to_string()})?;
let mut bytes = Vec::new();
class_file.read_to_end(&mut bytes).unwrap();
bytes
Ok(bytes)
}
fn runtime_class(&mut self, class_file: ClassFile) -> RuntimeClass {
@ -309,11 +316,21 @@ impl ClassLoader {
None
}
});
let line_number_table = code.as_ref().and_then(|x| {
x.attributes.iter().find_map(|attr| {
match constant_pool.parse_attribute(attr.clone()).ok()? {
Attribute::LineNumberTable(table) => Some(table.line_number_table),
_ => None,
}
})
});
MethodData {
name,
flags,
desc,
code,
line_number_table,
}
})
.collect::<Vec<_>>();
@ -339,26 +356,16 @@ impl ClassLoader {
.cloned()
.filter(|e| e.access_flags.INTERFACE)
.collect::<Vec<_>>();
// if super_classes.len() > 1 {
// println!("Jobs Done");
// }
// if super_interfaces.len() > interfaces.len() {
// let mut super_names = super_interfaces
// .iter()
// .cloned()
// .map(|e| e.this_class.clone())
// .collect::<Vec<_>>();
// super_names.sort();
// println!("sif: {:#?}", super_names);
// let mut names = interfaces
// .iter()
// .cloned()
// .map(|e| e.this_class.clone())
// .collect::<Vec<_>>();
// names.sort();
// println!("if: {:#?}", names);
// println!("Ready to work");
// }
let source_file = class_file.attributes.iter().find_map(|attr| {
match constant_pool.parse_attribute(attr.clone()).ok()? {
Attribute::SourceFile(index) => {
constant_pool.get_string(index).ok()
},
_ => None,
}
});
RuntimeClass {
constant_pool,
access_flags,
@ -372,6 +379,7 @@ impl ClassLoader {
super_classes,
super_interfaces,
component_type: None,
source_file,
}
}
// pub fn get_or_create_array_class(class_name: &str) -> RuntimeClass {
@ -386,35 +394,52 @@ impl ClassLoader {
pub fn create_array_class(
&mut self,
component: Arc<RuntimeClass>,
) -> RuntimeClass {
) -> Arc<RuntimeClass> {
// let name = format!("[{}", component.descriptor()); // e.g., "[Ljava/lang/String;"
let object_class: Arc<RuntimeClass> = self.get_or_load("/java/lang/Object", None).unwrap();
let cloneable: Arc<RuntimeClass> = self.get_or_load("/java/lang/Cloneable", None).unwrap();
let serializable: Arc<RuntimeClass> = self.get_or_load("/java/io/Serializable", None).unwrap();
RuntimeClass {
constant_pool: Arc::new(vec![]),
access_flags: ClassFlags {
MODULE: false,
ENUM: false,
ANNOTATION: false,
SYNTHETIC: false,
ABSTRACT: true,
INTERFACE: false,
SUPER: false,
FINAL: true,
PUBLIC: true,
},
this_class: "name".to_string(),
super_class: Some(object_class.clone()),
interfaces: vec![cloneable.clone(), serializable.clone()],
fields: vec![],
methods: vec![], // clone() is handled specially
mirror: OnceLock::new(),
init_state: Mutex::new(InitState::Initialized), // arrays need no <clinit>
super_classes: vec![object_class],
super_interfaces: vec![cloneable, serializable],
component_type: Some(component), // new field
}
let object_class: Arc<RuntimeClass> = self.get_or_load("java/lang/Object", None).unwrap();
let cloneable: Arc<RuntimeClass> = self.get_or_load("java/lang/Cloneable", None).unwrap();
let serializable: Arc<RuntimeClass> = self.get_or_load("java/io/Serializable", None).unwrap();
let name = match component.this_class.as_str() {
"byte" => "[B".to_string(),
"char" => "[C".to_string(),
"double" => "[D".to_string(),
"float" => "[F".to_string(),
"int" => "[I".to_string(),
"long" => "[J".to_string(),
"short" => "[S".to_string(),
"boolean" => "[Z".to_string(),
s if s.starts_with('[') => format!("[{}", s), // nested array
s => format!("[L{};", s), // object class
};
let klass =
RuntimeClass {
constant_pool: Arc::new(vec![]),
access_flags: ClassFlags {
MODULE: false,
ENUM: false,
ANNOTATION: false,
SYNTHETIC: false,
ABSTRACT: true,
INTERFACE: false,
SUPER: false,
FINAL: true,
PUBLIC: true,
},
this_class: name.clone(),
super_class: Some(object_class.clone()),
interfaces: vec![cloneable.clone(), serializable.clone()],
fields: vec![],
methods: vec![], // clone() is handled specially
mirror: OnceLock::new(),
init_state: Mutex::new(InitState::NotInitialized), // arrays need no <clinit>
super_classes: vec![object_class],
super_interfaces: vec![cloneable, serializable],
component_type: Some(component), // new field
source_file: None,
};
let klass = Arc::new(klass);
self.classes.insert((name.to_string(), None), klass.clone());
klass
}
pub fn primitive_class(&mut self, name: &str) -> Arc<RuntimeClass> {
@ -449,6 +474,7 @@ impl ClassLoader {
super_classes: vec![],
super_interfaces: vec![],
component_type: None,
source_file: None,
});
self.classes.insert((name.to_string(), None), klass.clone());

73
crates/core/src/error.rs Normal file
View File

@ -0,0 +1,73 @@
use std::fmt::{Display, Formatter};
use deku::DekuError;
use crate::class_file::constant_pool::ConstantPoolError;
#[derive(Debug)]
pub enum VmError {
ConstantPoolError(String),
StackError(String),
InvariantError(String),
DekuError(DekuError),
LoaderError(String),
ExecutionError,
NativeError(String),
Exception {
message: String,
stack_trace: Vec<StackTraceElement>,
},
}
impl VmError {
pub fn stack_not_int() -> Self {
Self::InvariantError("Value on stack was not an int".to_string())
}
}
impl Display for VmError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
VmError::ConstantPoolError(msg) => write!(f, "Constant pool error: {}", msg),
VmError::StackError(msg) => write!(f, "Stack error: {}", msg),
VmError::InvariantError(msg) => write!(f, "Invariant error: {}", msg),
VmError::DekuError(err) => write!(f, "Deku error: {}", err),
VmError::LoaderError(msg) => write!(f, "Loader error: {}", msg),
VmError::ExecutionError => write!(f, "Execution error"),
VmError::NativeError(msg) => write!(f, "Native error {msg}"),
_ => { write!(f, "idk lol") }
}
}
}
impl From<ConstantPoolError> for VmError {
fn from(value: ConstantPoolError) -> Self {
Self::ConstantPoolError(value.to_string())
}
}
impl From<DekuError> for VmError {
fn from(value: DekuError) -> Self {
Self::DekuError(value)
}
}
#[derive(Debug)]
pub struct StackTraceElement {
pub class: String,
pub method: String,
pub file: Option<String>,
pub line: Option<u16>,
}
impl VmError {
pub(crate) fn with_frame(self, elem: StackTraceElement) -> Self {
match self {
VmError::Exception { message, mut stack_trace } => {
stack_trace.push(elem);
VmError::Exception { message, stack_trace }
}
other => VmError::Exception {
message: format!("{}", other),
stack_trace: vec![elem],
},
}
}
}

View File

@ -1,17 +1,20 @@
#![allow(unused_variables)]
#![feature(c_variadic)]
use crate::class::RuntimeClass;
use crate::objects::array::ArrayReference;
use crate::objects::object_manager::ObjectManager;
use crate::prim::jboolean;
use crate::thread::VmThread;
use crate::value::{Primitive, Value};
use jni::objects::JClass;
use jni::sys::{jclass, jint, jobject, JNINativeInterface_};
use jni::sys::{jstring, JNIEnv};
use std::ffi::{c_char, CStr, CString};
use std::mem;
use std::ptr;
use std::sync::{Arc, Mutex, RwLock};
use std::sync::{Arc};
use crate::class_file::{FieldData, FieldRef};
use crate::objects::object::{ ObjectReference, ReferenceKind};
use crate::{BaseType, FieldType, MethodDescriptor};
use jni::sys::*;
use log::{error, info, trace, warn};
const JNI_VERSION_1_1: jint = 0x00010001;
const JNI_VERSION_1_2: jint = 0x00010002;
@ -413,11 +416,7 @@ unsafe extern "system" fn register_natives(
// JNI FUNCTION STUBS - All unimplemented functions below
// ============================================================================
use crate::class_file::{FieldData, FieldRef};
use crate::objects::object::{Object, ObjectReference, ReferenceKind};
use crate::{BaseType, FieldType, MethodDescriptor};
use jni::sys::*;
use log::{error, info, trace, warn};
unsafe extern "system" fn define_class(
env: *mut JNIEnv,
@ -1643,7 +1642,11 @@ unsafe extern "system" fn new_string_utf(env: *mut JNIEnv, utf: *const c_char) -
return ptr::null_mut();
};
let str_ref = thread.gc.write().unwrap().new_string(string_class, str);
let Ok(byte_array_class) = thread.get_class("[B") else {
return ptr::null_mut();
};
let str_ref = thread.gc.write().unwrap().new_string(byte_array_class, string_class, str);
str_ref
};
@ -1687,7 +1690,7 @@ unsafe extern "system" fn new_object_array(
return ptr::null_mut();
};
let ArrayReference::Object(arr_ref) = gc.new_object_array(len) else {
let ArrayReference::Object(arr_ref) = gc.new_object_array(runtime_class, len) else {
return ptr::null_mut();
};
let arr_id = arr_ref.lock().unwrap().id;

View File

@ -15,9 +15,10 @@
//! - [`MethodDescriptor`] - Method signature information
//! - [`FieldType`] - Field type information
use crate::error::{StackTraceElement, VmError};
use crate::value::{OperandStack, Primitive};
use crate::value::LocalVariables;
use crate::attributes::{ArrayType, Attribute, CodeAttribute};
use crate::attributes::{ArrayType, Attribute, CodeAttribute, LineNumberTableEntry};
use crate::class_file::constant_pool::ConstantPoolExt;
use crate::class_file::constant_pool::{ConstantPoolError, ConstantPoolGet};
use crate::class_file::{Bytecode, ClassFile, ConstantPoolEntry, MethodData, MethodRef};
@ -36,8 +37,9 @@ use std::fs::File;
use std::io::Read;
use std::ops::{BitAnd, Deref};
use std::sync::{Arc, Mutex};
use value::{Value};
use value::Value;
use vm::Vm;
use crate::class::RuntimeClass;
mod attributes;
mod bimage;
@ -54,6 +56,7 @@ mod rng;
mod thread;
pub mod value;
pub mod vm;
pub mod error;
// const NULL: Value = Value::Reference(None);
// include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
@ -177,6 +180,9 @@ struct Frame {
thread: Arc<VmThread>,
// The mod being invoked
method_ref: MethodRef,
class: Arc<RuntimeClass>,
line_number_table: Option<Vec<LineNumberTableEntry>>
}
impl Display for Frame {
@ -217,14 +223,29 @@ impl Frame {
Value::Padding => {panic!("We we pushing a pad?")}
}
}
fn current_line_number(&self) -> Option<u16> {
let table = self.line_number_table.as_ref()?;
// Find the entry where start_pc <= current pc
table.iter()
.rev()
.find(|entry| {
entry.start_pc as i64 <= self.pc
// (*start_pc as i64) <= self.pc)
})
.map(|entry| {
entry.line_number
})
}
// fn load_constant(index: u8) {}
fn new(
class: Arc<RuntimeClass>,
method_ref: MethodRef,
code_attr: CodeAttribute,
pool: Arc<Vec<ConstantPoolEntry>>,
mut locals: Vec<Value>,
locals: Vec<Value>,
vm: Arc<Vm>,
line_number_table: Option<Vec<LineNumberTableEntry>>
) -> Self {
// Get current thread from thread-local storage
let thread = VmThread::current(&vm);
@ -244,10 +265,12 @@ impl Frame {
bytecode,
thread,
method_ref,
class,
line_number_table
}
}
fn execute(&mut self) -> Result<Option<Value>, VmError> {
let binding = self.bytecode.code.clone();
fn execute(&mut self) -> Result<Option<Value>, error::VmError> {
let binding = self.bytecode.code.clone();
loop {
let (offset, op) = self.next().expect("No ops :(");
info!("pre set: {}", self.pc);
@ -265,14 +288,25 @@ impl Frame {
}
Ok(_) => self.pc += 1,
Err(x) => {
return Err(x.with_frame(StackTraceElement {
class: self.class.this_class.clone(),
method: self.method_ref.name.clone(),
file: self.class.source_file.clone(),
line: self.current_line_number(),
}));
eprintln!("[DEBUG] frame.thread.id = {:?}", self.thread.id);
eprintln!("[DEBUG] frame.thread.frame_stack.len = {}",
self.thread.frame_stack.lock().unwrap().len());
let objs = self.thread.gc.read().unwrap()
.objects
.iter()
.map(|(x, y)| format!("{x} : {y}"))
.collect::<Vec<_>>();
let len = objs.len().clone();
error!("Error in method: {:#?}", self.method_ref);
error!("Heap dump: len: {len} objs:\n{objs:#?}");
error!("Error in method: {:?}", self.method_ref);
error!("Error in VM: {x}");
self.thread.print_stack_trace();
panic!("Mission failed, we'll get em next time:\n{x}")
}
}
@ -290,7 +324,7 @@ impl Frame {
.join(", ")
);
}
Err(VmError::ExecutionError)
Err(error::VmError::ExecutionError)
}
fn next(&mut self) -> Option<(u16, Ops)> {
@ -491,53 +525,12 @@ enum ExecutionResult {
Return(()),
ReturnValue(Value),
}
#[derive(Debug)]
pub enum VmError {
ConstantPoolError(String),
StackError(String),
InvariantError(String),
DekuError(DekuError),
LoaderError(String),
ExecutionError,
NativeError(String),
}
impl VmError {
pub fn stack_not_int() -> Self {
Self::InvariantError("Value on stack was not an int".to_string())
}
}
impl Display for VmError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
VmError::ConstantPoolError(msg) => write!(f, "Constant pool error: {}", msg),
VmError::StackError(msg) => write!(f, "Stack error: {}", msg),
VmError::InvariantError(msg) => write!(f, "Invariant error: {}", msg),
VmError::DekuError(err) => write!(f, "Deku error: {}", err),
VmError::LoaderError(msg) => write!(f, "Loader error: {}", msg),
VmError::ExecutionError => write!(f, "Execution error"),
VmError::NativeError(msg) => write!(f, "Native error {msg}"),
}
}
}
impl From<ConstantPoolError> for VmError {
fn from(value: ConstantPoolError) -> Self {
Self::ConstantPoolError(value.to_string())
}
}
impl From<DekuError> for VmError {
fn from(value: DekuError) -> Self {
Self::DekuError(value)
}
}
impl Frame {
fn pop(&mut self) -> Result<Value, VmError>{
fn pop(&mut self) -> Result<Value, error::VmError>{
self.stack.pop()
}
fn execute_instruction(&mut self, op: Ops) -> Result<ExecutionResult, VmError> {
fn execute_instruction(&mut self, op: Ops) -> Result<ExecutionResult, error::VmError> {
match op {
Ops::nop => {
// TODO Should nop have any side effects?
@ -758,7 +751,23 @@ impl Frame {
Ok(ExecutionResult::Continue)
}
Ops::baload => {
todo!("boolean array load")
let Value::Primitive(Primitive::Int(index)) = self.pop()? else {
panic!("index on stack was not int")
};
let arr_ref = self.pop()?;
let value = match arr_ref {
Value::Reference(Some(ReferenceKind::ArrayReference(ArrayReference::Byte(arr)))) => {
let b = arr.lock().unwrap().get(index);
Value::from(b as i32)
}
Value::Reference(Some(ReferenceKind::ArrayReference(ArrayReference::Boolean(arr)))) => {
let b = arr.lock().unwrap().get(index);
Value::from(b as i32)
}
_ => panic!("Reference not on stack or not a byte/boolean array"),
};
self.push(value);
Ok(ExecutionResult::Continue)
}
Ops::caload => {
let Value::Primitive(Primitive::Int(index)) = self.pop()? else {
@ -771,7 +780,7 @@ impl Frame {
panic!("Reference not on stack or not a char array")
};
let c = arr.lock().unwrap().get(index);
self.push(Value::from(c));
self.push(Value::from(c as i32));
Ok(ExecutionResult::Continue)
}
Ops::saload => {
@ -886,7 +895,7 @@ impl Frame {
Ops::pop => {
let v1 = self.pop()?;
if v1.is_wide() {
Err(VmError::InvariantError("Op:pop on single wide value".to_string()))
Err(error::VmError::InvariantError("Op:pop on single wide value".to_string()))
} else { Ok(ExecutionResult::Continue) }
}
Ops::pop2 => {
@ -898,7 +907,7 @@ impl Frame {
let _v2 = self.pop()?;
let v1 = self.pop()?;
if v1.is_wide() {
Err(VmError::InvariantError("Op:pop2 popped a 2wide on second pop".to_string()))
Err(error::VmError::InvariantError("Op:pop2 popped a 2wide on second pop".to_string()))
} else { Ok(ExecutionResult::Continue) }
}
}
@ -906,7 +915,7 @@ impl Frame {
let v1 = self.pop()?;
let v2 = self.pop()?;
if v1.is_wide() || v2.is_wide() {
Err(VmError::InvariantError("dup_x1 operated on 2 wide value".to_string()))
Err(error::VmError::InvariantError("dup_x1 operated on 2 wide value".to_string()))
} else {
self.push(v1.clone());
self.push(v2);
@ -918,14 +927,14 @@ impl Frame {
let v1 = self.pop()?;
let v2 = self.pop()?;
if v1.is_wide() {
Err(VmError::InvariantError("dup_x2 operated on 1st 2 wide value".to_string()))
Err(error::VmError::InvariantError("dup_x2 operated on 1st 2 wide value".to_string()))
} else if v2.is_wide() {
self.push(v1.clone());
self.push(v2);
self.push(v1);
Ok(ExecutionResult::Continue)
} else if self.stack.peek()?.is_wide() {
Err(VmError::InvariantError("dup_x2 operated on 3rd 2 wide value".to_string()))
Err(error::VmError::InvariantError("dup_x2 operated on 3rd 2 wide value".to_string()))
}
else {
let v3 = self.pop()?;
@ -941,7 +950,7 @@ impl Frame {
self.stack.push(value.clone());
Ok(ExecutionResult::Continue)
} else {
Err(VmError::StackError("Stack underflow".to_string()))
Err(error::VmError::StackError("Stack underflow".to_string()))
}
}
Ops::dup2 => {
@ -951,7 +960,7 @@ impl Frame {
self.push(v1);
Ok(ExecutionResult::Continue)
} else if self.stack.peek()?.is_wide() {
Err(VmError::InvariantError("dup2 operated on 2nd, 2 wide value".to_string()))
Err(error::VmError::InvariantError("dup2 operated on 2nd, 2 wide value".to_string()))
} else {
let v2 = self.pop()?;
self.push(v2.clone());
@ -968,7 +977,7 @@ impl Frame {
// v1 is category 2, v2 must be category 1
let v2 = self.pop()?;
if v2.is_wide() {
Err(VmError::InvariantError("dup2_x1 form 2: v2 must be category 1".to_string()))
Err(error::VmError::InvariantError("dup2_x1 form 2: v2 must be category 1".to_string()))
} else {
self.push(v1.clone());
self.push(v2);
@ -980,11 +989,11 @@ impl Frame {
// all must be category 1
let v2 = self.pop()?;
if v2.is_wide() {
Err(VmError::InvariantError("dup2_x1 form 1: v2 must be category 1".to_string()))
Err(error::VmError::InvariantError("dup2_x1 form 1: v2 must be category 1".to_string()))
} else {
let v3 = self.pop()?;
if v3.is_wide() {
Err(VmError::InvariantError("dup2_x1 form 1: v3 must be category 1".to_string()))
Err(error::VmError::InvariantError("dup2_x1 form 1: v3 must be category 1".to_string()))
} else {
self.push(v2.clone());
self.push(v1.clone());
@ -1012,7 +1021,7 @@ impl Frame {
// v1 is category 2, v2 and v3 are category 1
let v3 = self.pop()?;
if v3.is_wide() {
Err(VmError::InvariantError("dup2_x2 form 2: v3 must be category 1".to_string()))
Err(error::VmError::InvariantError("dup2_x2 form 2: v3 must be category 1".to_string()))
} else {
self.push(v1.clone());
self.push(v3);
@ -1023,7 +1032,7 @@ impl Frame {
}
} else if v2.is_wide() {
// v1 category 1, v2 category 2 - no valid form for this
Err(VmError::InvariantError("dup2_x2: invalid - v1 cat1, v2 cat2".to_string()))
Err(error::VmError::InvariantError("dup2_x2: invalid - v1 cat1, v2 cat2".to_string()))
} else {
// v1 and v2 are both category 1
let v3 = self.pop()?;
@ -1041,7 +1050,7 @@ impl Frame {
// all category 1
let v4 = self.pop()?;
if v4.is_wide() {
Err(VmError::InvariantError("dup2_x2 form 1: v4 must be category 1".to_string()))
Err(error::VmError::InvariantError("dup2_x2 form 1: v4 must be category 1".to_string()))
} else {
self.push(v2.clone());
self.push(v1.clone());
@ -1058,7 +1067,7 @@ impl Frame {
let v1 = self.pop()?;
let v2 = self.pop()?;
if v1.is_wide() || v2.is_wide() {
Err(VmError::InvariantError("swap operated on 2 wide value".to_string()))
Err(error::VmError::InvariantError("swap operated on 2 wide value".to_string()))
} else {
self.push(v1);
self.push(v2);
@ -1125,7 +1134,7 @@ impl Frame {
self.vars.set(index as usize, new_val.into());
Ok(ExecutionResult::Continue)
} else {
Err(VmError::InvariantError("iinc requires integer value".to_string()))
Err(error::VmError::InvariantError("iinc requires integer value".to_string()))
}
}
@ -1151,10 +1160,10 @@ impl Frame {
// Comparisons
Ops::lcmp => {
let Value::Primitive(Primitive::Long(v2)) = self.pop()? else {
return Err(VmError::StackError("Expected long".into()));
return Err(error::VmError::StackError("Expected long".into()));
};
let Value::Primitive(Primitive::Long(v1)) = self.pop()? else {
return Err(VmError::StackError("Expected long".into()));
return Err(error::VmError::StackError("Expected long".into()));
};
let result: i32 = match v1.cmp(&v2) {
@ -1194,7 +1203,7 @@ impl Frame {
Ok(ExecutionResult::Continue)
}
} else {
Err(VmError::stack_not_int())
Err(error::VmError::stack_not_int())
}
}
Ops::if_acmpne(offset) => {
@ -1207,7 +1216,7 @@ impl Frame {
Ok(ExecutionResult::Continue)
}
} else {
Err(VmError::stack_not_int())
Err(error::VmError::stack_not_int())
}
}
// Control
@ -1240,7 +1249,7 @@ impl Frame {
Value::Primitive(Primitive::Char(v)) => v as i32,
Value::Primitive(Primitive::Short(v)) => v as i32,
_ => {
return Err(VmError::InvariantError(
return Err(error::VmError::InvariantError(
"ireturn requires integer-compatible value".to_owned(),
))
}
@ -1253,11 +1262,11 @@ impl Frame {
BaseType::Char => Ok(ExecutionResult::ReturnValue((x as u16).into())),
BaseType::Short => Ok(ExecutionResult::ReturnValue((x as i16).into())),
BaseType::Int => Ok(ExecutionResult::ReturnValue(x.into())),
_ => Err(VmError::InvariantError(
_ => Err(error::VmError::InvariantError(
"wrong return instruction for method".to_owned(),
)),
},
_ => Err(VmError::InvariantError(
_ => Err(error::VmError::InvariantError(
"wrong return instruction for method".to_owned(),
)),
}
@ -1266,28 +1275,28 @@ impl Frame {
let val = self.pop()?;
match val {
Value::Primitive(Primitive::Long(_)) => Ok(ExecutionResult::ReturnValue(val)),
_ => Err(VmError::StackError("Expected reference".into())),
_ => Err(error::VmError::StackError("Expected reference".into())),
}
}
Ops::freturn => {
let val = self.pop()?;
match val {
Value::Primitive(Primitive::Float(_)) => Ok(ExecutionResult::ReturnValue(val)),
_ => Err(VmError::StackError("Expected reference".into())),
_ => Err(error::VmError::StackError("Expected reference".into())),
}
}
Ops::dreturn => {
let val = self.pop()?;
match val {
Value::Primitive(Primitive::Double(_)) => Ok(ExecutionResult::ReturnValue(val)),
_ => Err(VmError::StackError("Expected reference".into())),
_ => Err(error::VmError::StackError("Expected reference".into())),
}
}
Ops::areturn => {
let val = self.pop()?;
match val {
Value::Reference(_) => Ok(ExecutionResult::ReturnValue(val)),
_ => Err(VmError::StackError("Expected reference".into())),
_ => Err(error::VmError::StackError("Expected reference".into())),
}
}
Ops::return_void => Ok(ExecutionResult::Return(())),
@ -1298,8 +1307,10 @@ impl Frame {
// can init the field
Ops::getstatic(index) => {
let field_ref = self.pool.resolve_field(index)?;
if field_ref.class.contains("jdk/internal/misc/VM") || field_ref.name.contains("initLevel") {
return Err(VmError::InvariantError("Intentional crash".to_string()))
}
println!("Getting static field {field_ref:?}");
let init_class = self
.thread
.get_or_resolve_class(&field_ref.class)
@ -1339,7 +1350,7 @@ impl Frame {
let popped = self.pop()?;
match popped {
Value::Primitive(x) => {
Err(VmError::StackError("Getfield era".parse().unwrap()))
Err(error::VmError::StackError("Getfield era".parse().unwrap()))
}
Value::Reference(x) => {
match x {
@ -1352,7 +1363,7 @@ impl Frame {
Ok(ExecutionResult::Continue)
}
ReferenceKind::ArrayReference(_) => {
Err(VmError::StackError("get field".parse().unwrap()))
Err(error::VmError::StackError("get field".parse().unwrap()))
}
},
}
@ -1393,10 +1404,10 @@ impl Frame {
object.lock().unwrap().set_field(&field_ref.name, value);
Ok(ExecutionResult::Continue)
} else {
Err(VmError::StackError("Null pointer exception".to_string()))
Err(error::VmError::StackError("Null pointer exception".to_string()))
}
} else {
Err(VmError::StackError(
Err(error::VmError::StackError(
"putfield tried to operate on a non object stack value".to_string(),
))
}
@ -1405,6 +1416,7 @@ impl Frame {
}
Ops::invokevirtual(index) => {
let method_ref = self.pool.resolve_method_ref(index)?;
// the 1 represents the receiver
let args_count = method_ref.desc.arg_width() + 1;
let args = self.stack.pop_n(args_count)?;
@ -1412,7 +1424,7 @@ impl Frame {
if let Some(val) = result {
self.push(val)
}
// todo!("Finish invoke virtual");
Ok(ExecutionResult::Continue)
}
@ -1428,7 +1440,7 @@ impl Frame {
if let Some(val) = result {
self.push(val)
}
// todo!("invoke special");
Ok(ExecutionResult::Continue)
}
@ -1446,8 +1458,21 @@ impl Frame {
Ok(ExecutionResult::Continue)
}
Ops::invokeinterface(_, _, _) => {
todo!("invokeInterface")
Ops::invokeinterface(index, count, _zero) => {
let method_ref = self.pool.resolve_interface_method_ref(index)?;
// the 1 represents the receiver
let args_count = method_ref.desc.arg_width() + 1;
let args = self.stack.pop_n(args_count)?;
let refe = args.first().expect("Must have reciever").as_ref_kind().expect("Must be ref");
let class = refe.class();
let result = self.thread.invoke_virtual(method_ref, class.clone(), args)?;
if let Some(val) = result {
self.push(val)
}
Ok(ExecutionResult::Continue)
}
Ops::invokedynamic(_, _) => {
@ -1469,6 +1494,11 @@ impl Frame {
}
Ops::newarray(array_type) => {
let array_class = self.thread.get_or_resolve_class(&array_type.to_string())?;
let value = self.stack.pop().expect("value to have stack");
let Value::Primitive(Primitive::Int(count)) = value else {
panic!("stack item was not int")
@ -1478,7 +1508,7 @@ impl Frame {
.gc
.write()
.unwrap()
.new_primitive_array(array_type.clone(), count);
.new_primitive_array(array_class, array_type.clone(), count);
self.stack
.push(Value::Reference(Some(ReferenceKind::ArrayReference(array))));
Ok(ExecutionResult::Continue)
@ -1486,12 +1516,12 @@ impl Frame {
Ops::anewarray(index) => {
let class_name = self.pool.resolve_class_name(index)?;
println!("{}", class_name);
// let array_class = self.thread.loader.get_or_create_array_class(class_name)?;
let array_class = self.thread.get_or_resolve_class(&class_name)?;
let value = self.stack.pop().expect("value to have stack");
let Value::Primitive(Primitive::Int(count)) = value else {
panic!("stack item was not int")
};
let array = self.thread.gc.write().unwrap().new_object_array(count);
let array = self.thread.gc.write().unwrap().new_object_array(array_class, count);
self.stack
.push(Value::Reference(Some(ReferenceKind::ArrayReference(array))));
Ok(ExecutionResult::Continue)
@ -1516,10 +1546,19 @@ impl Frame {
match x {
ReferenceKind::ObjectReference(obj) => {
if obj.lock().unwrap().class.is_assignable_into(into_class) { self.push(popped); Ok(ExecutionResult::Continue) } else { todo!("Error path") }
}
ReferenceKind::ArrayReference(arr) => {
todo!("Arrays")
let array_class = arr.class();
if array_class.is_assignable_into(into_class.clone()) {
self.push(popped);
Ok(ExecutionResult::Continue)
} else {
Err(VmError::Exception {
message: format!("ClassCastException: {} cannot be cast to {}",
array_class.this_class, into_class.this_class),
stack_trace: vec![],
})
}
}
}
} else { self.push(popped); Ok(ExecutionResult::Continue) }
@ -1535,7 +1574,13 @@ impl Frame {
Ok(ExecutionResult::Continue)
}
ReferenceKind::ArrayReference(arr) => {
todo!("Arrays")
let array_class = arr.class();
if array_class.is_assignable_into(into_class) {
self.push(1i32.into());
} else {
self.push(0i32.into());
}
Ok(ExecutionResult::Continue)
}
}
} else { panic!("yeet") }
@ -1561,7 +1606,7 @@ impl Frame {
Ok(ExecutionResult::Continue)
}
} else {
Err(VmError::stack_not_int())
Err(error::VmError::stack_not_int())
}
}
Ops::ifnonnull(offset) => {
@ -1572,7 +1617,7 @@ impl Frame {
Ok(ExecutionResult::Continue)
}
} else {
Err(VmError::stack_not_int())
Err(error::VmError::stack_not_int())
}
}
Ops::goto_w(_) => {
@ -1593,7 +1638,7 @@ impl Frame {
}
}
fn load_constant(&mut self, index: u16) -> Result<ExecutionResult, VmError> {
fn load_constant(&mut self, index: u16) -> Result<ExecutionResult, error::VmError> {
let thing = self.pool.get_constant(index.to_owned())?;
trace!("\tLoading constant: {}", thing);
let resolved = match thing {
@ -1602,7 +1647,7 @@ impl Frame {
ConstantPoolEntry::Class(x) => {
let name = self.pool.get_string(x.name_index)?;
let class = self.thread.get_or_resolve_class(&name)?;
let class_ref = self.thread.gc.read().unwrap().get(*class.mirror.wait());
let class_ref = self.thread.gc.read().unwrap().get(*class.mirror.get().expect(&format!("Mirror unintialised {}", class.this_class)));
Value::from(class_ref)
}
ConstantPoolEntry::String(x) => {

View File

@ -1,7 +1,8 @@
use roast_vm_core::vm::Vm;
use roast_vm_core::error::VmError;
use libloading::Library;
use log::LevelFilter;
use log::{error, LevelFilter};
use colored::Colorize;
fn main() {
env_logger::Builder::from_default_env()
.filter_level(LevelFilter::Trace)
@ -14,7 +15,42 @@ fn main() {
vm.load_native_library("roast_vm.dll", load("roast_vm.dll").into());
vm.load_native_library("jvm.dll", load("jvm.dll").into());
vm.load_native_library("java.dll", load("java.dll").into());
vm.run("org/example/Main");
match vm.run("org/example/Main") {
Ok(_) => {}
Err(VmError::Exception { message, stack_trace }) => {
let thread = vm.threads.get(&vm.main_thread_id).unwrap();
let objs = thread.gc.read().unwrap()
.objects
.iter()
.map(|(x, y)| format!("{x} : {y}"))
.collect::<Vec<_>>();
let len = objs.len().clone();
error!("Heap dump: len: {len} objs:\n{objs:#?}");
eprintln!("{}: {}", "Exception".red().bold(), message);
for elem in &stack_trace {
let class_name = elem.class.replace('/', ".");
let at = "\tat".dimmed();
let location = match (&elem.file, elem.line) {
(Some(f), Some(l)) => format!("({}:{})", f, l),
(Some(f), None) => format!("({})", f),
_ => "(Unknown Source)".to_string(),
}.blue().dimmed();
eprintln!("{} {}.{}{}", at, class_name, elem.method, location);
}
/*error!("Exception: {}", message);
for elem in &stack_trace {
let class_name = elem.class.replace('/', ".");
match (&elem.file, elem.line) {
(Some(f), Some(l)) => eprintln!("\tat {}.{}({}:{})", class_name, elem.method, f, l),
(Some(f), None) => eprintln!("\tat {}.{}({})", class_name, elem.method, f),
_ => eprintln!("\tat {}.{}(Unknown Source)", class_name, elem.method),
}
}*/
}
Err(e) => {
error!("VM Error: {:?}", e);
}
}
}
fn load(filename: &str) -> Library {

View File

@ -3,6 +3,8 @@ use crate::prim::Primitive;
use jni::sys::{jboolean, jbyte, jchar, jdouble, jfloat, jint, jlong, jshort};
use std::sync::{Arc, Mutex};
use std::ops::{Deref, DerefMut};
use crate::class::RuntimeClass;
use crate::error::VmError;
#[derive(Debug, Clone)]
pub enum ArrayReference {
@ -45,6 +47,20 @@ impl ArrayReference {
ArrayReference::Object(x) => x.lock().unwrap().id,
}
}
pub fn class(&self) -> Arc<RuntimeClass> {
match self {
ArrayReference::Int(x) => x.lock().unwrap().class.clone(),
ArrayReference::Byte(x) => x.lock().unwrap().class.clone(),
ArrayReference::Short(x) => x.lock().unwrap().class.clone(),
ArrayReference::Long(x) => x.lock().unwrap().class.clone(),
ArrayReference::Float(x) => x.lock().unwrap().class.clone(),
ArrayReference::Double(x) => x.lock().unwrap().class.clone(),
ArrayReference::Char(x) => x.lock().unwrap().class.clone(),
ArrayReference::Boolean(x) => x.lock().unwrap().class.clone(),
ArrayReference::Object(x) => x.lock().unwrap().class.clone(),
}
}
}
pub type PrimitiveArrayReference<T: Primitive> = Arc<Mutex<Array<T>>>;
@ -54,6 +70,7 @@ pub type ObjectArrayReference = Arc<Mutex<Array<Option<ReferenceKind>>>>;
#[derive(Debug)]
pub struct Array<T: ArrayValue> {
pub(crate) id: u32,
pub(crate) class: Arc<RuntimeClass>,
pub(crate) backing: Box<[T]>,
}
@ -69,9 +86,10 @@ where
self.backing[index as usize].clone()
}
pub fn new(id: u32, length: i32) -> Self {
pub fn new(id: u32, class: Arc<RuntimeClass>, length: i32) -> Self {
Self {
id,
class,
backing: vec![T::default(); length as usize].into_boxed_slice(),
}
}
@ -81,11 +99,12 @@ where
}
}
impl<T: Primitive + ArrayValue> From<(u32, Vec<T>)> for Array<T> {
fn from(value: (u32, Vec<T>)) -> Self {
let (id, vector) = value;
impl<T: Primitive + ArrayValue> From<(u32, Arc<RuntimeClass>, Vec<T>)> for Array<T> {
fn from(value: (u32, Arc<RuntimeClass>, Vec<T>)) -> Self {
let (id, class, vector) = value;
Self {
id,
class,
backing: vector.into_boxed_slice(),
}
}
@ -119,3 +138,83 @@ impl ArrayValue for jfloat {}
impl ArrayValue for jdouble {}
impl ArrayValue for jboolean {}
impl ArrayReference {
pub fn copy_from(
&self,
src: &ArrayReference,
src_pos: jint,
dst_pos: jint,
length: jint,
) -> Result<(), VmError> {
macro_rules! copy {
($src_arr:expr, $dst_arr:expr) => {{
let src_guard = $src_arr.lock().unwrap();
let mut dst_guard = $dst_arr.lock().unwrap();
let src_start = src_pos as usize;
let dst_start = dst_pos as usize;
let len = length as usize;
// Bounds check
if src_pos < 0 || dst_pos < 0 || length < 0
|| src_start + len > src_guard.backing.len()
|| dst_start + len > dst_guard.backing.len()
{
return Err(VmError::InvariantError("Index oob".to_string()));
}
if Arc::ptr_eq($src_arr, $dst_arr) {
drop(src_guard);
dst_guard.backing.copy_within(src_start..src_start + len, dst_start);
} else {
dst_guard.backing[dst_start..dst_start + len]
.copy_from_slice(&src_guard.backing[src_start..src_start + len]);
}
Ok(())
}};
}
use ArrayReference::*;
match (src, self) {
(Int(s), Int(d)) => copy!(s, d),
(Byte(s), Byte(d)) => copy!(s, d),
(Short(s), Short(d)) => copy!(s, d),
(Long(s), Long(d)) => copy!(s, d),
(Float(s), Float(d)) => copy!(s, d),
(Double(s), Double(d)) => copy!(s, d),
(Char(s), Char(d)) => copy!(s, d),
(Boolean(s), Boolean(d)) => copy!(s, d),
(Object(s), Object(d)) => {
// Object arrays need clone, not copy
let src_guard = s.lock().unwrap();
let mut dst_guard = d.lock().unwrap();
let src_start = src_pos as usize;
let dst_start = dst_pos as usize;
let len = length as usize;
if src_pos < 0 || dst_pos < 0 || length < 0
|| src_start + len > src_guard.backing.len()
|| dst_start + len > dst_guard.backing.len()
{
return Err(VmError::InvariantError("Index oob".to_string()));
}
if Arc::ptr_eq(s, d) {
drop(src_guard);
for i in if src_start < dst_start { (0..len).rev().collect::<Vec<_>>() } else { (0..len).collect() } {
dst_guard.backing[dst_start + i] = dst_guard.backing[src_start + i].clone();
}
} else {
for i in 0..len {
dst_guard.backing[dst_start + i] = src_guard.backing[src_start + i].clone();
}
}
Ok(())
}
_ => Err(VmError::InvariantError("Array type mismatch".to_string())),
}
}
}

View File

@ -110,6 +110,12 @@ impl ReferenceKind {
Self::ArrayReference(a) => a.id(),
}
}
pub fn class(&self) -> Arc<RuntimeClass> {
match self {
Self::ObjectReference(r) => r.lock().unwrap().class.clone(),
Self::ArrayReference(a) => a.class(),
}
}
}
pub type Reference = Option<ReferenceKind>;

View File

@ -33,7 +33,7 @@ impl ObjectManager {
object
}
pub fn new_primitive_array(&mut self, array_type: ArrayType, count: i32) -> ArrayReference {
pub fn new_primitive_array(&mut self, class: Arc<RuntimeClass>, array_type: ArrayType, count: i32) -> ArrayReference {
let id = generate_identity_hash();
assert!(
!self.objects.contains_key(&id),
@ -41,21 +41,21 @@ impl ObjectManager {
);
let array_ref = match array_type {
ArrayType::T_INT => ArrayReference::Int(Arc::new(Mutex::new(Array::new(id, count)))),
ArrayType::T_BYTE => ArrayReference::Byte(Arc::new(Mutex::new(Array::new(id, count)))),
ArrayType::T_INT => ArrayReference::Int(Arc::new(Mutex::new(Array::new(id, class,count)))),
ArrayType::T_BYTE => ArrayReference::Byte(Arc::new(Mutex::new(Array::new(id, class,count)))),
ArrayType::T_SHORT => {
ArrayReference::Short(Arc::new(Mutex::new(Array::new(id, count))))
ArrayReference::Short(Arc::new(Mutex::new(Array::new(id, class,count))))
}
ArrayType::T_LONG => ArrayReference::Long(Arc::new(Mutex::new(Array::new(id, count)))),
ArrayType::T_LONG => ArrayReference::Long(Arc::new(Mutex::new(Array::new(id, class,count)))),
ArrayType::T_FLOAT => {
ArrayReference::Float(Arc::new(Mutex::new(Array::new(id, count))))
ArrayReference::Float(Arc::new(Mutex::new(Array::new(id, class,count))))
}
ArrayType::T_DOUBLE => {
ArrayReference::Double(Arc::new(Mutex::new(Array::new(id, count))))
ArrayReference::Double(Arc::new(Mutex::new(Array::new(id, class,count))))
}
ArrayType::T_CHAR => ArrayReference::Char(Arc::new(Mutex::new(Array::new(id, count)))),
ArrayType::T_CHAR => ArrayReference::Char(Arc::new(Mutex::new(Array::new(id, class,count)))),
ArrayType::T_BOOLEAN => {
ArrayReference::Boolean(Arc::new(Mutex::new(Array::new(id, count))))
ArrayReference::Boolean(Arc::new(Mutex::new(Array::new(id, class,count))))
}
};
@ -64,26 +64,26 @@ impl ObjectManager {
array_ref
}
pub fn new_object_array(&mut self, count: i32) -> ArrayReference {
pub fn new_object_array(&mut self, class: Arc<RuntimeClass>, count: i32) -> ArrayReference {
let id = generate_identity_hash();
assert!(
!self.objects.contains_key(&id),
"Generated ID already exists!"
);
let array_ref = ArrayReference::Object(Arc::new(Mutex::new(Array::new(id, count))));
let array_ref = ArrayReference::Object(Arc::new(Mutex::new(Array::new(id, class,count))));
self.objects
.insert(id, ReferenceKind::ArrayReference(array_ref.clone()));
array_ref
}
pub fn new_byte_array(&mut self, vector: Vec<i8>) -> ArrayReference {
pub fn new_byte_array(&mut self, class: Arc<RuntimeClass>, vector: Vec<i8>) -> ArrayReference {
warn!("Manual sidechannel byte array creation");
let id = generate_identity_hash();
assert!(
!self.objects.contains_key(&id),
"Generated ID already exists!"
);
let array_ref = ArrayReference::Byte(Arc::new(Mutex::new(Array::from((id, vector)))));
let array_ref = ArrayReference::Byte(Arc::new(Mutex::new(Array::from((id, class,vector)))));
self.objects
.insert(id, ReferenceKind::ArrayReference(array_ref.clone()));
@ -111,7 +111,7 @@ impl ObjectManager {
.and_then(ReferenceKind::into_object_reference)
}
pub fn new_string(&mut self, string_class: Arc<RuntimeClass>, utf8: &str) -> ObjectReference {
pub fn new_string(&mut self, byte_class: Arc<RuntimeClass>, string_class: Arc<RuntimeClass>, utf8: &str) -> ObjectReference {
warn!("Manual sidechannel string creation: \n\"{}\"", utf8);
let key = utf8.to_owned();
let jstr = self.new_object(string_class);
@ -120,7 +120,7 @@ impl ObjectManager {
.flat_map(|e| e.to_le_bytes())
.map(|e| e as i8)
.collect::<Vec<_>>();
let barray = self.new_byte_array(byte_vec);
let barray = self.new_byte_array(byte_class, byte_vec);
jstr.lock().unwrap().fields.insert(
"value".to_string(),

View File

@ -6,7 +6,7 @@ use crate::objects::object::{ObjectReference, ReferenceKind};
use crate::objects::object_manager::ObjectManager;
use crate::value::{Primitive, Value};
use crate::vm::Vm;
use crate::{BaseType, FieldType, Frame, MethodDescriptor, ThreadId, VmError};
use crate::{BaseType, FieldType, Frame, MethodDescriptor, ThreadId};
use deku::DekuError::Incomplete;
use itertools::Itertools;
use jni::sys::{jboolean, jbyte, jchar, jdouble, jfloat, jint, jlong, jobject, jshort, JNIEnv};
@ -21,6 +21,7 @@ use std::ptr::null_mut;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Arc, Mutex, RwLock};
use std::vec::IntoIter;
use crate::error::VmError;
type MethodCallResult = Result<Option<Value>, VmError>;
@ -36,7 +37,7 @@ pub struct VmThread {
pub id: ThreadId,
pub vm: Arc<Vm>,
pub loader: Arc<Mutex<ClassLoader>>,
pub frame_stack: Vec<Frame>,
pub frame_stack: Mutex<Vec<Arc<Mutex<Frame>>>>,
pub gc: Arc<RwLock<ObjectManager>>,
pub jni_env: JNIEnv,
}
@ -54,7 +55,7 @@ impl VmThread {
id,
vm,
loader,
frame_stack: Vec::new(),
frame_stack: Default::default(),
gc,
jni_env,
}
@ -87,8 +88,7 @@ impl VmThread {
.loader
.lock()
.unwrap()
.get_or_load(what, None)
.map_err(VmError::LoaderError)?;
.get_or_load(what, None)?;
// Phase 2: Collect classes that need initialisation (short lock)
let classes_to_init = {
@ -111,7 +111,6 @@ impl VmThread {
.lock()
.unwrap()
.get_or_load(what, None)
.map_err(VmError::LoaderError)
}
/// Initialize a class following JVM Spec 5.5.
@ -178,23 +177,8 @@ impl VmThread {
}
// Run <clinit> if present
let class_init_method = class.find_method("<clinit>", &MethodDescriptor::void());
if let Ok(method) = class_init_method {
let method_ref = MethodRef {
class: class.this_class.clone(),
name: method.name.clone(),
desc: method.desc.clone(),
};
Frame::new(
method_ref,
method.code.clone().unwrap(),
class.constant_pool.clone(),
vec![],
self.vm.clone(),
)
.execute()
.map_err(|e| VmError::LoaderError(format!("Error in <clinit>: {:?}", e)))?;
if let Ok(method) = class.find_method("<clinit>", &MethodDescriptor::void()) {
self.execute_method(&class, &method, vec![])?;
}
Ok(())
})();
@ -216,18 +200,32 @@ impl VmThread {
result
}
pub fn invoke_main(&self, what: &str) {
pub fn invoke_main(&self, what: &str) -> Result<(), VmError> {
let method_ref = MethodRef {
class: what.to_string(),
name: "main".to_string(),
desc: MethodDescriptor::psvm(),
};
self.invoke(method_ref, Vec::new())
.expect("Main method died");
self.invoke(method_ref, Vec::new())?;
Ok(())
}
pub fn invoke(&self, method_reference: MethodRef, mut args: Vec<Value>) -> MethodCallResult {
pub fn invoke(&self, method_reference: MethodRef, args: Vec<Value>) -> MethodCallResult {
if method_reference.class.contains("Unsafe") {
println!("()")
}
let class = self.get_or_resolve_class(&method_reference.class)?;
let method = class.find_method(&method_reference.name, &method_reference.desc).unwrap();
self.execute_method(&class, &method, args)
}
pub fn invoke_virtual(&self, method_reference: MethodRef, class: Arc<RuntimeClass>, args: Vec<Value>) -> MethodCallResult {
let method = class.find_method(&method_reference.name, &method_reference.desc).unwrap();
self.execute_method(&class, &method, args)
}
/*pub fn invoke_old(&self, method_reference: MethodRef, mut args: Vec<Value>) -> MethodCallResult {
let mut new_args = Vec::new();
let class = self.get_or_resolve_class(&method_reference.class)?;
let resolved_method = class
@ -255,8 +253,11 @@ impl VmThread {
args,
self.vm.clone(),
);
frame.execute()
}
self.frame_stack.lock().unwrap().push(frame.clone());
let result = frame.execute()?;
self.frame_stack.lock().unwrap().pop();
Ok(result)
}*/
pub fn invoke_native(&self, method: &MethodRef, mut args: Vec<Value>) -> MethodCallResult {
let symbol_name = generate_jni_method_name(method, false);
@ -275,7 +276,7 @@ impl VmThread {
let name_with_params = generate_jni_method_name(method, true);
self.vm.find_native_method(&name_with_params)
})
.ok_or(VmError::NativeError("Link error".to_owned()))?;
.ok_or(VmError::NativeError(format!("Link error: Unable to locate symbol {symbol_name}")))?;
// build pointer to native fn
let cp = CodePtr::from_ptr(p);
@ -351,6 +352,70 @@ impl VmThread {
warn!("Invoke native not final");
result
}
fn execute_method(
&self,
class: &Arc<RuntimeClass>,
method: &MethodData,
args: Vec<Value>,
) -> MethodCallResult {
eprintln!("[DEBUG] execute_method self.id = {:?}", self.id);
let method_ref = MethodRef {
class: class.this_class.clone(),
name: method.name.clone(),
desc: method.desc.clone(),
};
if method.flags.ACC_NATIVE {
let mut native_args = Vec::new();
if method.flags.ACC_STATIC {
let jclass = self.vm.gc.read().unwrap().get(*class.mirror.wait());
native_args.push(Value::Reference(Some(jclass)));
}
native_args.extend(args);
return self.invoke_native(&method_ref, native_args);
}
let mut frame = Frame::new(
class.clone(),
method_ref,
method.code.clone().unwrap(),
class.constant_pool.clone(),
args,
self.vm.clone(),
method.line_number_table.clone(),
);
let frame = Arc::new(Mutex::new(frame));
self.frame_stack.lock().unwrap().push(frame.clone());
eprintln!("[DEBUG] pushed frame for {}.{}, stack depth now: {}",
class.this_class, method.name,
self.frame_stack.lock().unwrap().len());
let result = frame.lock().unwrap().execute();
eprintln!("[DEBUG] returned from {}.{}, result ok: {}, stack depth: {}",
class.this_class, method.name, result.is_ok(),
self.frame_stack.lock().unwrap().len());
if result.is_ok() {
self.frame_stack.lock().unwrap().pop();
}
result
}
pub fn print_stack_trace(&self) {
let guard = self.frame_stack.lock().unwrap();
// Reverse - most recent frame first (like Java does)
for frame in guard.iter().rev() {
let frame = frame.lock().unwrap();
let method = &frame.method_ref;
// Internal format uses '/', Java stack traces use '.'
let class_name = method.class.replace("/", ".");
match (&frame.class.source_file, &frame.current_line_number()) {
(Some(file), Some(line)) => eprintln!("\tat {}.{}({}:{})", class_name, method.name, file, line),
(Some(file), None) => eprintln!("\tat {}.{}({})", class_name, method.name, file),
_ => eprintln!("\tat {}.{}(Unknown Source)", class_name, method.name),
}
}
}
}
fn build_args<'a>(
@ -478,6 +543,7 @@ impl VmThread {
}
let string_class = self.get_class("java/lang/String").unwrap();
gc.new_string(string_class, utf)
let byte_array_class = self.get_class("[B").unwrap();
gc.new_string(byte_array_class, string_class, utf)
}
}

View File

@ -1,12 +1,13 @@
use crate::objects::array::ArrayReference;
use crate::objects::object::{ObjectReference, ReferenceKind};
use crate::{BaseType, FieldType, VmError};
use crate::{BaseType, FieldType};
use core::fmt;
use dashmap::DashMap;
use jni::sys::{jboolean, jbyte, jchar, jdouble, jfloat, jint, jlong, jshort};
use log::trace;
use std::fmt::{Display, Formatter};
use std::ops::Deref;
use crate::error::VmError;
/// A reference-counted, thread-safe pointer to an Object.
@ -167,6 +168,13 @@ impl Value {
pub fn is_wide(&self) -> bool {
matches!(self, Value::Primitive(Primitive::Long(_) | Primitive::Double(_)))
}
pub fn as_ref_kind(&self) -> Option<ReferenceKind> {
match self {
Value::Reference(Some(kind)) => Some(kind.clone()),
_ => None,
}
}
}
macro_rules! impl_value_from {

View File

@ -11,12 +11,12 @@ use crate::class_file::{ClassFlags, MethodRef};
use crate::class_loader::ClassLoader;
use crate::objects::object_manager::ObjectManager;
use crate::thread::VmThread;
use crate::{MethodDescriptor, ThreadId, VmError};
use crate::{MethodDescriptor, ThreadId};
use dashmap::DashMap;
use imp::{Library, Symbol};
use imp::Library;
use std::sync::{Arc, Mutex, RwLock};
use crate::class::{InitState, RuntimeClass};
use crate::objects::object::ReferenceKind;
use crate::error::VmError;
// struct AbstractObject<'a> {}
pub struct Vm {
@ -108,25 +108,45 @@ impl Vm {
// "jdk/internal/misc/UnsafeConstants"
];
let _ = classes.iter().map(|e| thread.get_or_resolve_class(e));
let prims = vec!["byte", "char", "double", "float", "int", "long", "short", "boolean"];
let prims = vec![
("byte", "B"),
("char", "C"),
("double", "D"),
("float", "F"),
("int", "I"),
("long", "J"),
("short", "S"),
("boolean", "Z")
];
let thread = self.threads.get(&self.main_thread_id).unwrap();
for prim in prims {
let klass =
self.loader.lock().unwrap().primitive_class(prim);
self.loader.lock().unwrap().primitive_class(prim.0);
let class_class = thread.get_class("java/lang/Class")?;
let string = thread.intern_string(&prim);
let name_obj = thread.intern_string(&prim.0);
let flags = ClassFlags::from(1041u16);
let class_obj = self.gc.write().unwrap().new_class(
class_class.clone(),
Some(ReferenceKind::ObjectReference(name_obj)),
None,
flags,
true
);
klass.mirror.set(class_obj.lock().unwrap().id).unwrap();
let prim_array_klass = self.loader.lock().unwrap().create_array_class(klass.clone());
let arr_name_obj = thread.intern_string(&prim_array_klass.this_class);
let arr_class_obj = self.gc.write().unwrap().new_class(
class_class,
Some(ReferenceKind::ObjectReference(string)),
Some(ReferenceKind::ObjectReference(arr_name_obj)),
None,
flags,
false
);
klass.mirror.set(class_obj.lock().unwrap().id).unwrap();
prim_array_klass.mirror.set(arr_class_obj.lock().unwrap().id).unwrap();
}
let phase1ref = MethodRef {
@ -138,8 +158,8 @@ impl Vm {
Ok(())
}
pub fn run(&self, what: &str) {
self.boot_strap().expect("Failed to bootstrap vm!");
pub fn run(&self, what: &str) -> Result<(), VmError> {
self.boot_strap()?;
// Get main thread from DashMap
let thread = self.threads.get(&self.main_thread_id).unwrap().clone();
thread.invoke_main(what)

View File

@ -7,6 +7,7 @@ publish = ["nexus"]
[dependencies]
jni = { workspace = true }
roast-vm-core = { workspace = true }
log = "0.4.28"
[lib]
name = "roast_vm"

View File

@ -3,6 +3,7 @@
mod class;
mod object;
mod misc_unsafe;
mod system;
use jni::objects::{JClass, JObject, JString};
use jni::strings::JNIString;
@ -11,6 +12,7 @@ use jni::{JNIEnv, NativeMethod};
use std::ffi::c_void;
use std::io::Write;
use std::time::{SystemTime, UNIX_EPOCH};
use roast_vm_core::objects::array::ArrayReference;
use roast_vm_core::objects::object::ObjectReference;
use roast_vm_core::objects::ReferenceKind;
use roast_vm_core::VmThread;
@ -161,6 +163,14 @@ fn resolve_object(thread: &VmThread, obj: jobject) -> Option<ObjectReference> {
Some(obj_ref.clone())
}
fn resolve_array(thread: &VmThread, obj: jobject) -> Option<ArrayReference> {
let gc = thread.gc.read().unwrap();
let ReferenceKind::ArrayReference(arr_ref) = gc.get(obj as u32) else {
return None;
};
Some(arr_ref.clone())
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_jdk_internal_util_SystemProps_00024Raw_vmProperties<'local>(
mut env: JNIEnv<'local>,

View File

@ -1,4 +1,5 @@
use jni::sys::{jclass, jint, jobject, jstring, JNIEnv};
use log::warn;
use crate::{get_thread, resolve_object};
#[unsafe(no_mangle)]
@ -8,4 +9,22 @@ pub unsafe extern "system" fn Java_jdk_internal_misc_Unsafe_registerNatives(
) {
//no op
()
}
#[unsafe(no_mangle)]
pub unsafe extern "system" fn Java_jdk_internal_misc_Unsafe_arrayBaseOffset0(
env: *mut JNIEnv,
obj: jclass,
) -> jint {
warn!("arrayBaseOffset0 currently just returning 0");
0
}
#[unsafe(no_mangle)]
pub unsafe extern "system" fn Java_jdk_internal_misc_Unsafe_arrayIndexScale0(
env: *mut JNIEnv,
obj: jclass,
) -> jint {
warn!("arrayIndexScale0 currently just returning 0");
0
}

View File

@ -0,0 +1,51 @@
use jni::sys::{jclass, jint, jobject, JNIEnv};
use log::warn;
use crate::{get_thread, resolve_array, resolve_object};
#[unsafe(no_mangle)]
pub unsafe extern "system" fn Java_java_lang_System_arraycopy(
env: *mut JNIEnv,
_ignored: jclass,
src: jobject,
src_pos: jint,
dst: jobject,
dst_pos: jint,
length: jint,
) {
let thread = &*get_thread(env);
// Check for null pointers
if src.is_null() || dst.is_null() {
panic!("NPE!");
// throw_exception(env, "java/lang/NullPointerException", None);
return;
}
// Resolve JNI handles to actual objects
let src_arr = resolve_array(thread, src).expect("Was tested non null");
let dst_arr = resolve_array(thread, dst).expect("Was tested non null");
// Validate both are arrays
/*let (Some(src_arr), Some(dst_arr)) = (src_obj.as_array(), dst_obj.as_array()) else {
panic!("not arrays!");
throw_exception(env, "java/lang/ArrayStoreException", None);
return;
};*/
let src_len = src_arr.len();
let dst_len = dst_arr.len();
// Bounds checking
if src_pos < 0 || dst_pos < 0 || length < 0
|| src_pos.checked_add(length).map_or(true, |end| end > src_len)
|| dst_pos.checked_add(length).map_or(true, |end| end > dst_len)
{
panic!("Array index out of bounds!");
// throw_exception(env, "java/lang/ArrayIndexOutOfBoundsException", None);
return;
}
dst_arr.copy_from(&src_arr, src_pos, dst_pos, length).expect("Array copy error hell");
// Type compatibility check + copy
}