From bf259da7c9203f3d040d6cacb5a3448cded811d6 Mon Sep 17 00:00:00 2001 From: james Date: Wed, 10 Dec 2025 18:08:35 +1030 Subject: [PATCH] Stack Traces --- crates/core/Cargo.toml | 1 + crates/core/src/attributes.rs | 18 +- crates/core/src/bimage.rs | 4 +- crates/core/src/class.rs | 25 +- crates/core/src/class_file/class_file.rs | 3 +- crates/core/src/class_file/constant_pool.rs | 21 +- crates/core/src/class_loader.rs | 148 +++++++----- crates/core/src/error.rs | 73 ++++++ crates/core/src/jni.rs | 25 +- crates/core/src/lib.rs | 239 ++++++++++++-------- crates/core/src/main.rs | 42 +++- crates/core/src/objects/array.rs | 107 ++++++++- crates/core/src/objects/object.rs | 6 + crates/core/src/objects/object_manager.rs | 30 +-- crates/core/src/thread.rs | 128 ++++++++--- crates/core/src/value.rs | 10 +- crates/core/src/vm.rs | 40 +++- crates/roast-vm-sys/Cargo.toml | 1 + crates/roast-vm-sys/src/lib.rs | 10 + crates/roast-vm-sys/src/misc_unsafe.rs | 19 ++ crates/roast-vm-sys/src/system.rs | 51 +++++ 21 files changed, 753 insertions(+), 248 deletions(-) create mode 100644 crates/core/src/error.rs create mode 100644 crates/roast-vm-sys/src/system.rs diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml index 222fcbe..0a39ea2 100644 --- a/crates/core/Cargo.toml +++ b/crates/core/Cargo.toml @@ -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" diff --git a/crates/core/src/attributes.rs b/crates/core/src/attributes.rs index acf74ef..715576d 100644 --- a/crates/core/src/attributes.rs +++ b/crates/core/src/attributes.rs @@ -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 for Ops { // type Error = (); // diff --git a/crates/core/src/bimage.rs b/crates/core/src/bimage.rs index 08e03bb..7befd70 100644 --- a/crates/core/src/bimage.rs +++ b/crates/core/src/bimage.rs @@ -79,7 +79,7 @@ impl Bimage { slashes.replace("/", ".") } - pub fn get_class(&mut self, module: &str, class: &str) -> Option> { + pub fn get_class(&mut self, module: &str, class: &str) -> Result, 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() } } diff --git a/crates/core/src/class.rs b/crates/core/src/class.rs index 15c5d15..046a5b1 100644 --- a/crates/core/src/class.rs +++ b/crates/core/src/class.rs @@ -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>, pub super_interfaces: Vec>, pub component_type: Option>, + pub source_file: Option, } impl Hash for RuntimeClass { fn hash(&self, state: &mut H) { @@ -98,8 +100,23 @@ impl RuntimeClass { } pub fn is_assignable_into(&self, into: Arc) -> 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") } } diff --git a/crates/core/src/class_file/class_file.rs b/crates/core/src/class_file/class_file.rs index f14e521..179e5a9 100644 --- a/crates/core/src/class_file/class_file.rs +++ b/crates/core/src/class_file/class_file.rs @@ -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, pub flags: MethodFlags, + pub line_number_table: Option>, // pub exceptions: Option<_>, // pub visible_annotations: Option<_>, // pub invisible_annotations: Option<_>, diff --git a/crates/core/src/class_file/constant_pool.rs b/crates/core/src/class_file/constant_pool.rs index c1643ba..3f8fd4b 100644 --- a/crates/core/src/class_file/constant_pool.rs +++ b/crates/core/src/class_file/constant_pool.rs @@ -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; @@ -100,6 +97,16 @@ pub trait ConstantPoolExt: ConstantPoolGet { Ok(MethodRef { class, name, desc }) } + fn resolve_interface_method_ref(&self, index: u16) -> Result { + 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 { let desc = self.get_string(method.descriptor_index)?; diff --git a/crates/core/src/class_loader.rs b/crates/core/src/class_loader.rs index 3c25ea9..1529cac 100644 --- a/crates/core/src/class_loader.rs +++ b/crates/core/src/class_loader.rs @@ -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>; @@ -141,7 +142,7 @@ impl ClassLoader { &mut self, class_name: &str, loader: LoaderId, - ) -> Result, String> { + ) -> Result, 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, String> { + fn load_class(&mut self, what: &str, loader: LoaderId) -> Result, 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::>(); + // 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 { + fn load_class_from_disk(what: &str) -> Result, 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::>(); @@ -339,26 +356,16 @@ impl ClassLoader { .cloned() .filter(|e| e.access_flags.INTERFACE) .collect::>(); - // 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::>(); - // super_names.sort(); - // println!("sif: {:#?}", super_names); - // let mut names = interfaces - // .iter() - // .cloned() - // .map(|e| e.this_class.clone()) - // .collect::>(); - // 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 { + ) -> Arc { // let name = format!("[{}", component.descriptor()); // e.g., "[Ljava/lang/String;" - let object_class: Arc = self.get_or_load("/java/lang/Object", None).unwrap(); - let cloneable: Arc = self.get_or_load("/java/lang/Cloneable", None).unwrap(); - let serializable: Arc = 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 - super_classes: vec![object_class], - super_interfaces: vec![cloneable, serializable], - component_type: Some(component), // new field - } + let object_class: Arc = self.get_or_load("java/lang/Object", None).unwrap(); + let cloneable: Arc = self.get_or_load("java/lang/Cloneable", None).unwrap(); + let serializable: Arc = 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 + 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 { @@ -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()); diff --git a/crates/core/src/error.rs b/crates/core/src/error.rs new file mode 100644 index 0000000..578fe09 --- /dev/null +++ b/crates/core/src/error.rs @@ -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, + }, +} + +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 for VmError { + fn from(value: ConstantPoolError) -> Self { + Self::ConstantPoolError(value.to_string()) + } +} + +impl From for VmError { + fn from(value: DekuError) -> Self { + Self::DekuError(value) + } +} +#[derive(Debug)] +pub struct StackTraceElement { + pub class: String, + pub method: String, + pub file: Option, + pub line: Option, +} + +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], + }, + } + } +} diff --git a/crates/core/src/jni.rs b/crates/core/src/jni.rs index dabc82f..0a60102 100644 --- a/crates/core/src/jni.rs +++ b/crates/core/src/jni.rs @@ -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; diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs index 0ae04b2..22d5b51 100644 --- a/crates/core/src/lib.rs +++ b/crates/core/src/lib.rs @@ -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, // The mod being invoked method_ref: MethodRef, + + class: Arc, + line_number_table: Option> } impl Display for Frame { @@ -217,14 +223,29 @@ impl Frame { Value::Padding => {panic!("We we pushing a pad?")} } } + fn current_line_number(&self) -> Option { + 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, method_ref: MethodRef, code_attr: CodeAttribute, pool: Arc>, - mut locals: Vec, + locals: Vec, vm: Arc, + line_number_table: Option> ) -> 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, VmError> { - let binding = self.bytecode.code.clone(); + fn execute(&mut self) -> Result, 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::>(); 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 for VmError { - fn from(value: ConstantPoolError) -> Self { - Self::ConstantPoolError(value.to_string()) - } -} -impl From for VmError { - fn from(value: DekuError) -> Self { - Self::DekuError(value) - } -} impl Frame { - fn pop(&mut self) -> Result{ + fn pop(&mut self) -> Result{ self.stack.pop() } - fn execute_instruction(&mut self, op: Ops) -> Result { + fn execute_instruction(&mut self, op: Ops) -> Result { 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 { + fn load_constant(&mut self, index: u16) -> Result { 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) => { diff --git a/crates/core/src/main.rs b/crates/core/src/main.rs index 6e1f33c..31bb546 100644 --- a/crates/core/src/main.rs +++ b/crates/core/src/main.rs @@ -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::>(); + 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 { diff --git a/crates/core/src/objects/array.rs b/crates/core/src/objects/array.rs index ada0bbb..e99b8ce 100644 --- a/crates/core/src/objects/array.rs +++ b/crates/core/src/objects/array.rs @@ -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 { + 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 = Arc>>; @@ -54,6 +70,7 @@ pub type ObjectArrayReference = Arc>>>; #[derive(Debug)] pub struct Array { pub(crate) id: u32, + pub(crate) class: Arc, 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, length: i32) -> Self { Self { id, + class, backing: vec![T::default(); length as usize].into_boxed_slice(), } } @@ -81,11 +99,12 @@ where } } -impl From<(u32, Vec)> for Array { - fn from(value: (u32, Vec)) -> Self { - let (id, vector) = value; +impl From<(u32, Arc, Vec)> for Array { + fn from(value: (u32, Arc, Vec)) -> 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::>() } 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())), + } + } +} \ No newline at end of file diff --git a/crates/core/src/objects/object.rs b/crates/core/src/objects/object.rs index 36fb294..2cd06f1 100644 --- a/crates/core/src/objects/object.rs +++ b/crates/core/src/objects/object.rs @@ -110,6 +110,12 @@ impl ReferenceKind { Self::ArrayReference(a) => a.id(), } } + pub fn class(&self) -> Arc { + match self { + Self::ObjectReference(r) => r.lock().unwrap().class.clone(), + Self::ArrayReference(a) => a.class(), + } + } } pub type Reference = Option; diff --git a/crates/core/src/objects/object_manager.rs b/crates/core/src/objects/object_manager.rs index 914df4f..8d98851 100644 --- a/crates/core/src/objects/object_manager.rs +++ b/crates/core/src/objects/object_manager.rs @@ -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, 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, 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) -> ArrayReference { + pub fn new_byte_array(&mut self, class: Arc, vector: Vec) -> 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, utf8: &str) -> ObjectReference { + pub fn new_string(&mut self, byte_class: Arc, string_class: Arc, 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::>(); - 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(), diff --git a/crates/core/src/thread.rs b/crates/core/src/thread.rs index 7e5a597..8a46145 100644 --- a/crates/core/src/thread.rs +++ b/crates/core/src/thread.rs @@ -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, VmError>; @@ -36,7 +37,7 @@ pub struct VmThread { pub id: ThreadId, pub vm: Arc, pub loader: Arc>, - pub frame_stack: Vec, + pub frame_stack: Mutex>>>, pub gc: Arc>, 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 if present - let class_init_method = class.find_method("", &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 : {:?}", e)))?; + if let Ok(method) = class.find_method("", &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) -> MethodCallResult { + pub fn invoke(&self, method_reference: MethodRef, args: Vec) -> 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, args: Vec) -> 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) -> 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) -> 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, + method: &MethodData, + args: Vec, + ) -> 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) } } diff --git a/crates/core/src/value.rs b/crates/core/src/value.rs index 7e35738..f8df6e0 100644 --- a/crates/core/src/value.rs +++ b/crates/core/src/value.rs @@ -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 { + match self { + Value::Reference(Some(kind)) => Some(kind.clone()), + _ => None, + } + } } macro_rules! impl_value_from { diff --git a/crates/core/src/vm.rs b/crates/core/src/vm.rs index 8e129f4..5a8d9f4 100644 --- a/crates/core/src/vm.rs +++ b/crates/core/src/vm.rs @@ -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) diff --git a/crates/roast-vm-sys/Cargo.toml b/crates/roast-vm-sys/Cargo.toml index 9b70229..606b2a5 100644 --- a/crates/roast-vm-sys/Cargo.toml +++ b/crates/roast-vm-sys/Cargo.toml @@ -7,6 +7,7 @@ publish = ["nexus"] [dependencies] jni = { workspace = true } roast-vm-core = { workspace = true } +log = "0.4.28" [lib] name = "roast_vm" diff --git a/crates/roast-vm-sys/src/lib.rs b/crates/roast-vm-sys/src/lib.rs index 7ae7243..a4a2bfa 100644 --- a/crates/roast-vm-sys/src/lib.rs +++ b/crates/roast-vm-sys/src/lib.rs @@ -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 { Some(obj_ref.clone()) } +fn resolve_array(thread: &VmThread, obj: jobject) -> Option { + 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>, diff --git a/crates/roast-vm-sys/src/misc_unsafe.rs b/crates/roast-vm-sys/src/misc_unsafe.rs index 8927bd0..fbb8dd2 100644 --- a/crates/roast-vm-sys/src/misc_unsafe.rs +++ b/crates/roast-vm-sys/src/misc_unsafe.rs @@ -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 } \ No newline at end of file diff --git a/crates/roast-vm-sys/src/system.rs b/crates/roast-vm-sys/src/system.rs new file mode 100644 index 0000000..67d248b --- /dev/null +++ b/crates/roast-vm-sys/src/system.rs @@ -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 +} \ No newline at end of file