diff --git a/crates/core/src/bimage.rs b/crates/core/src/bimage.rs index 070a268..982e7a3 100644 --- a/crates/core/src/bimage.rs +++ b/crates/core/src/bimage.rs @@ -1,8 +1,7 @@ -use log::trace; use sevenz_rust2::ArchiveReader; use std::collections::{HashMap, HashSet}; use std::fs::File; -use std::ops::{Add, AddAssign}; +use std::ops::AddAssign; use std::time::{Duration, Instant}; const DEFAULT_LOCATION: &str = "./lib/modules"; @@ -31,21 +30,6 @@ impl Default for Bimage { .into_iter() .collect::>(); modules.sort(); - - /*let packages = reader - .archive() - .files - .iter() - .filter(|e| !e.is_directory) - .map(|e| { - if e.name.contains("java.base") { - println!("{:?}", e); - } - - ("Greg".to_owned(), "Dave".to_owned()) - }) - .collect::>();*/ - let packages = HashMap::new(); Self { @@ -85,9 +69,6 @@ impl Bimage { pub fn get_class(&mut self, module: &str, class: &str) -> Result, String> { // trace!("Modules{:#?}", self.modules); - if class.contains("ScopedMemoryAccess") { - println!("Time to scoped: {:?}", self.total_access_time) - } let start = Instant::now(); let path = Self::resolve_path(module, class); let res = self.image.read_file(&path).map_err(|e| { diff --git a/crates/core/src/class.rs b/crates/core/src/class.rs index 7a2336c..114f5e8 100644 --- a/crates/core/src/class.rs +++ b/crates/core/src/class.rs @@ -1,15 +1,12 @@ -use crate::attributes::AttributeInfo; -use crate::class_file::{ - ClassFile, ClassFlags, ConstantPoolEntry, FieldData, FieldInfo, FieldRef, MethodData, - MethodInfo, MethodRef, -}; +use crate::class_file::attributes::BootstrapMethodsAttribute; +use crate::class_file::{ClassFlags, ConstantPoolEntry, FieldData, MethodData}; use crate::error::VmError; use crate::{FieldType, MethodDescriptor}; use log::trace; use parking_lot::Mutex; use std::hash::{Hash, Hasher}; use std::sync::atomic::AtomicBool; -use std::sync::{Arc, OnceLock, OnceState}; +use std::sync::{Arc, OnceLock}; use std::thread::ThreadId; /// JVM Spec 5.5: Initialization states for a class @@ -25,6 +22,8 @@ pub enum InitState { Error(String), } +pub type ClassRef = Arc; + #[derive(Debug)] pub struct RuntimeClass { pub constant_pool: Arc>, @@ -42,6 +41,7 @@ pub struct RuntimeClass { pub super_interfaces: Vec>, pub component_type: Option>, pub source_file: Option, + pub bootstrap_attribute: Option, } impl Hash for RuntimeClass { fn hash(&self, state: &mut H) { diff --git a/crates/core/src/attributes.rs b/crates/core/src/class_file/attributes.rs similarity index 82% rename from crates/core/src/attributes.rs rename to crates/core/src/class_file/attributes.rs index d4bc6b3..a622aaf 100644 --- a/crates/core/src/attributes.rs +++ b/crates/core/src/class_file/attributes.rs @@ -1,11 +1,10 @@ use crate::class_file::constant_pool::ConstantPoolExt; -use crate::class_file::{ClassFile, Constant, ConstantPoolEntry}; -use deku::{DekuContainerRead, DekuError, DekuReader}; -use deku_derive::DekuRead; -use log::trace; -use std::fmt::{Display, Formatter}; -use std::ops::Deref; +use crate::class_file::{ClassFile, Constant}; use deku::ctx::Endian; +use deku::{DekuError, DekuReader}; +use deku_derive::DekuRead; +use std::fmt::{Display, Formatter}; +use std::str::FromStr; #[derive(Clone, PartialEq, Debug, DekuRead)] #[deku(ctx = "_endian: deku::ctx::Endian", endian = "big")] @@ -24,7 +23,7 @@ pub enum Attribute { ConstantValue(Constant), Code(CodeAttribute), StackMapTable(Vec), - BootstrapMethods, + BootstrapMethods(BootstrapMethodsAttribute), NestHost, NestMembers, PermittedSubclasses, @@ -61,6 +60,30 @@ pub enum ArrayType { T_LONG, } +impl FromStr for ArrayType { + type Err = String; + + fn from_str(s: &str) -> Result { + match s { + // Integral types + "int" => Ok(Self::T_INT), + "long" => Ok(Self::T_LONG), + "short" => Ok(Self::T_SHORT), + "char" => Ok(Self::T_CHAR), + "byte" => Ok(Self::T_BYTE), + + // Floating-point types + "float" => Ok(Self::T_FLOAT), + "double" => Ok(Self::T_DOUBLE), + + // Other types + "boolean" => Ok(Self::T_BOOLEAN), + + _ => Err("Invalid primitive type name".to_string()), + } + } +} + #[derive(Clone, PartialEq, Debug)] pub struct LookupSwitchData { pub default: i32, @@ -123,12 +146,17 @@ impl<'a> DekuReader<'a, (Endian, u16)> for TableSwitchData { offsets.push(i32::from_reader_with_ctx(reader, Endian::Big)?); } - Ok(TableSwitchData { default, low, high, offsets }) + Ok(TableSwitchData { + default, + low, + high, + offsets, + }) } } impl Display for ArrayType { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { let str = match self { ArrayType::T_BOOLEAN => "[Z", ArrayType::T_CHAR => "[C", @@ -143,22 +171,6 @@ impl Display for ArrayType { } } -// impl TryFrom for Ops { -// type Error = (); -// -// fn try_from(value: u8) -> Result { -// match value { -// 0x2A => Ok(Ops::Aload0), -// 0x2B => Ok(Ops::InvokeSpecial(0)), -// 0x10 => Ok(Ops::Bipush(0)), -// 0xB5 => Ok(Ops::Putfield(0)), -// 0x14 => Ok(Ops::Ldc2_w(0)), -// 0xB1 => Ok(Ops::Retern), -// _ => Err(()) -// } -// } -// } - impl AttributeInfo { // pub fn parse_attribute(&self, constant_pool: &[ConstantPoolEntry]) -> Option { // let name = crate::class_file::pool_get_string(constant_pool, self.attribute_name_index)?; @@ -226,7 +238,7 @@ impl AttributeInfo { impl LocalVariableTableAttribute {} impl Display for AttributeInfo { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { writeln!( f, "AttributeInfo {{ name_index: {}, length: {} }}", @@ -236,7 +248,7 @@ impl Display for AttributeInfo { } impl Display for Attribute { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { Attribute::Code(code) => write!(f, "Code attribute: {}", code), Attribute::SourceFile(index) => write!(f, "SourceFile attribute, index: {}", index), @@ -252,6 +264,9 @@ impl Display for Attribute { Attribute::LocalVariableTable(table) => { write!(f, "LocalVariableTable attribute: {}", table) } + Attribute::BootstrapMethods(bootstrap) => { + write!(f, "BootstrapMethods attribute: {}", bootstrap) + } Attribute::Unknown(name, data) => { write!(f, "Unknown attribute '{}', {} bytes", name, data.len()) } @@ -263,7 +278,7 @@ impl Display for Attribute { } impl Display for CodeAttribute { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!( f, "stack={}, locals={}, code={} bytes, exceptions={}, attributes={}", @@ -320,7 +335,7 @@ pub struct LocalVariableTableEntry { } impl Display for LocalVariableTableAttribute { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!( f, "local_variable_table_length={}, entries={}", @@ -338,6 +353,23 @@ pub struct LineNumberTableAttribute { pub line_number_table: Vec, } +#[derive(Clone, PartialEq, Debug, DekuRead)] +#[deku(endian = "big")] +pub struct BootstrapMethodsAttribute { + pub num_bootstrap_methods: u16, + #[deku(count = "num_bootstrap_methods")] + pub bootstrap_methods: Vec, +} + +#[derive(Clone, PartialEq, Debug, DekuRead)] +#[deku(ctx = "_endian: deku::ctx::Endian", endian = "big")] +pub struct BootstrapMethod { + pub bootstrap_method_ref: u16, + pub num_bootstrap_arguments: u16, + #[deku(count = "num_bootstrap_arguments")] + pub bootstrap_arguments: Vec, +} + #[derive(Clone, PartialEq, Debug, DekuRead)] #[deku(ctx = "_endian: deku::ctx::Endian", endian = "big")] pub struct LineNumberTableEntry { @@ -346,7 +378,7 @@ pub struct LineNumberTableEntry { } impl Display for LineNumberTableAttribute { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!( f, "table_length={}, entries={}", @@ -355,3 +387,14 @@ impl Display for LineNumberTableAttribute { ) } } + +impl Display for BootstrapMethodsAttribute { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!( + f, + "num_bootstrap_methods={}, methods={}", + self.num_bootstrap_methods, + self.bootstrap_methods.len() + ) + } +} diff --git a/crates/core/src/class_file/class_file.rs b/crates/core/src/class_file/class_file.rs index 441fa2f..d42938b 100644 --- a/crates/core/src/class_file/class_file.rs +++ b/crates/core/src/class_file/class_file.rs @@ -1,6 +1,7 @@ -use crate::attributes::{Attribute, AttributeInfo, CodeAttribute, LineNumberTableEntry}; -use crate::class::RuntimeClass; -use crate::class_file::constant_pool::{ConstantPoolError, ConstantPoolExt, ConstantPoolOwned}; +use crate::class_file::attributes::{ + Attribute, AttributeInfo, CodeAttribute, LineNumberTableEntry, +}; +use crate::class_file::constant_pool::{ConstantPoolExt, ConstantPoolOwned}; use crate::instructions::Ops; use crate::value::Value; use crate::{BaseType, FieldType, MethodDescriptor}; @@ -214,8 +215,8 @@ pub struct ConstantPackageInfo { } // Display implementations for better formatting -impl fmt::Display for ClassFile { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { +impl Display for ClassFile { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { writeln!(f, "Class File Information:")?; writeln!( f, @@ -266,8 +267,8 @@ impl fmt::Display for ClassFile { } } -impl fmt::Display for ConstantPoolEntry { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { +impl Display for ConstantPoolEntry { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match self { ConstantPoolEntry::Utf8(info) => { let s = String::from_utf8_lossy(&info.bytes); @@ -323,8 +324,8 @@ impl fmt::Display for ConstantPoolEntry { } } -impl fmt::Display for FieldInfo { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { +impl Display for FieldInfo { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { write!( f, "flags=0x{:04X}, name=#{}, descriptor=#{}, attrs={}", @@ -336,8 +337,8 @@ impl fmt::Display for FieldInfo { } } -impl fmt::Display for MethodInfo { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { +impl Display for MethodInfo { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { let attrs: Vec<_> = self .attributes .iter() @@ -395,7 +396,7 @@ impl ClassFile { expanded } - fn format_attribute(&self, f: &mut fmt::Formatter<'_>, attr: &AttributeInfo) -> fmt::Result { + fn format_attribute(&self, f: &mut Formatter<'_>, attr: &AttributeInfo) -> fmt::Result { let attribute = attr .get(self) .unwrap_or_else(|| panic!("Failed to parse attribute {}", attr)); @@ -512,7 +513,7 @@ fn read_bytecode_with_offsets( let op = Ops::from_reader_with_ctx(reader, byte_offset)?; let end_pos = reader.bits_read; - let op_bytes = ((end_pos - start_pos) / 8) as usize; + let op_bytes = (end_pos - start_pos) / 8; code.push((byte_offset, op)); byte_offset += op_bytes as u16; @@ -678,6 +679,12 @@ impl From for MethodRef { } } +impl From<&MethodData> for MethodRef { + fn from(value: &MethodData) -> Self { + value.clone().into() + } +} + #[derive(Debug)] pub struct FieldRef { pub class: String, @@ -685,6 +692,18 @@ pub struct FieldRef { pub desc: FieldType, } +impl FieldRef { + pub fn new(class: &str, name: &str, field: FieldType) -> Self { + let class = class.to_string(); + let name = name.to_string(); + Self { + class, + name, + desc: field, + } + } +} + #[derive(Debug)] pub struct FieldData { pub name: String, @@ -699,19 +718,38 @@ pub enum Constant { Long(i64), Float(f32), Double(f64), - String(String), + String(u16), } -impl From for Value { - fn from(value: Constant) -> Self { +// impl From for Value { +// fn from(value: Constant) -> Self { +// match value { +// Constant::Int(x) => x.into(), +// Constant::Long(x) => x.into(), +// Constant::Float(x) => x.into(), +// Constant::Double(x) => x.into(), +// Constant::String(x) => { +// todo!("Constant string") +// } +// } +// } +// } + +impl From<&ConstantPoolEntry> for Constant { + fn from(value: &ConstantPoolEntry) -> Self { + value.clone().into() + } +} + +impl From for Constant { + fn from(value: ConstantPoolEntry) -> Self { match value { - Constant::Int(x) => x.into(), - Constant::Long(x) => x.into(), - Constant::Float(x) => x.into(), - Constant::Double(x) => x.into(), - Constant::String(x) => { - todo!("Constant string") - } + ConstantPoolEntry::Integer(i) => Self::Int(i), + ConstantPoolEntry::Float(f) => Self::Float(f), + ConstantPoolEntry::Long(l) => Self::Long(l), + ConstantPoolEntry::Double(d) => Self::Double(d), + ConstantPoolEntry::String(s) => Self::String(s.string_index), + _ => panic!(), } } } diff --git a/crates/core/src/class_file/constant_pool.rs b/crates/core/src/class_file/constant_pool.rs index d2d8e6e..d8afa01 100644 --- a/crates/core/src/class_file/constant_pool.rs +++ b/crates/core/src/class_file/constant_pool.rs @@ -1,5 +1,6 @@ -use crate::attributes::{ - Attribute, AttributeInfo, CodeAttribute, LineNumberTableAttribute, LocalVariableTableAttribute, +use crate::class_file::attributes::{ + Attribute, AttributeInfo, BootstrapMethodsAttribute, CodeAttribute, LineNumberTableAttribute, + LocalVariableTableAttribute, }; use crate::class_file::{ ConstantClassInfo, ConstantDynamicInfo, ConstantFieldrefInfo, ConstantInterfaceMethodrefInfo, @@ -9,8 +10,8 @@ use crate::class_file::{ MethodRef, }; use crate::error::VmError; -use crate::{pool_get_impl, FieldType, MethodDescriptor}; -use cesu8::{from_java_cesu8, Cesu8DecodingError}; +use crate::{FieldType, MethodDescriptor, pool_get_impl}; +use cesu8::{Cesu8DecodingError, from_java_cesu8}; use deku::DekuContainerRead; use std::fmt::{Display, Formatter}; @@ -137,8 +138,12 @@ pub trait ConstantPoolExt: ConstantPoolGet { // trace!("Parsing attribute with name: {}", name); match name.as_ref() { + "ConstantValue" => { + let thing = self.get_constant(u16::from_be_bytes([a.info[0], a.info[1]]))?; + Ok(Attribute::ConstantValue(thing.into())) + } "Code" => { - let (_, mut code_attr) = CodeAttribute::from_bytes((a.info.as_slice(), 0))?; + let (_, code_attr) = CodeAttribute::from_bytes((a.info.as_slice(), 0))?; Ok(Attribute::Code(code_attr)) } "SourceFile" => { @@ -160,6 +165,10 @@ pub trait ConstantPoolExt: ConstantPoolGet { let (_, lvt) = LocalVariableTableAttribute::from_bytes((&a.info.as_slice(), 0))?; Ok(Attribute::LocalVariableTable(lvt)) } + "BootstrapMethods" => { + let (_, bsm) = BootstrapMethodsAttribute::from_bytes((&a.info.as_slice(), 0))?; + Ok(Attribute::BootstrapMethods(bsm)) + } _ => Ok(Attribute::Unknown(name.to_string(), a.info.clone())), } } diff --git a/crates/core/src/class_file/mod.rs b/crates/core/src/class_file/mod.rs index 440960b..b67033e 100644 --- a/crates/core/src/class_file/mod.rs +++ b/crates/core/src/class_file/mod.rs @@ -1,6 +1,5 @@ +pub mod attributes; pub mod class_file; pub mod constant_pool; -// Re-export items if you want them accessible directly from class_file:: pub use class_file::*; // optional - // pub use constant_pool::*; // optional diff --git a/crates/core/src/class_loader.rs b/crates/core/src/class_loader.rs index fd66dc0..5fb751d 100644 --- a/crates/core/src/class_loader.rs +++ b/crates/core/src/class_loader.rs @@ -1,8 +1,10 @@ -use crate::attributes::Attribute; use crate::bimage::Bimage; use crate::class::{InitState, RuntimeClass}; +use crate::class_file::attributes::Attribute; use crate::class_file::constant_pool::{ConstantPoolExt, ConstantPoolGet}; -use crate::class_file::{ClassFile, ClassFlags, FieldData, FieldFlags, MethodData, MethodFlags}; +use crate::class_file::{ + ClassFile, ClassFlags, Constant, FieldData, FieldFlags, MethodData, MethodFlags, +}; use crate::error::VmError; use crate::{FieldType, MethodDescriptor}; use dashmap::DashMap; @@ -85,7 +87,7 @@ pub fn resolve_path(what: &str) -> Result<(PathBuf, String), String> { #[derive(Default)] pub struct ClassLoader { pub(crate) classes: DashMap<(String, LoaderId), Arc>, - bimage: Bimage, + bimage: Option, // pub needs_init: Vec>, } @@ -93,10 +95,12 @@ type LoaderId = Option; impl ClassLoader { pub fn access_time(&self) -> Duration { - self.bimage.total_access_time + self.bimage + .as_ref() + .map_or(Duration::ZERO, |b| b.total_access_time) } pub fn new() -> Result { - let loader = Self::default(); + let loader = Self::with_bimage(); // for entry in VM_CLASSES { // let module_class = format!("{}/{}", "java.base", entry);; // loader.load_class(&module_class)?; @@ -104,6 +108,14 @@ impl ClassLoader { Ok(loader) } + /// Create a ClassLoader with the boot image loaded + pub fn with_bimage() -> Self { + Self { + classes: DashMap::new(), + bimage: Some(Bimage::default()), + } + } + pub fn class_from_mirror_id(&self, id: u32) -> Option> { self.classes .iter() @@ -147,37 +159,34 @@ impl ClassLoader { class_name: &str, loader: LoaderId, ) -> Result, VmError> { + let class_name = match class_name { + "B" => "byte", + "C" => "char", + "D" => "double", + "F" => "float", + "I" => "int", + "J" => "long", + "S" => "short", + "Z" => "boolean", + _ => class_name, + }; + + // Normalize L-descriptors: Ljava/lang/String; -> java/lang/String + let class_name = class_name + .strip_prefix('L') + .and_then(|s| s.strip_suffix(';')) + .unwrap_or(class_name); + if let Some(class) = self.classes.get(&(class_name.to_string(), loader)) { return Ok(class.clone()); } - if class_name.starts_with("[") { - let component_name = class_name.strip_prefix("[").unwrap(); - let component = match component_name.chars().next() { - Some('B') => self.get_or_load("byte", None), - Some('C') => self.get_or_load("char", None), - Some('D') => self.get_or_load("double", None), - Some('F') => self.get_or_load("float", None), - Some('I') => self.get_or_load("int", None), - Some('J') => self.get_or_load("long", None), - Some('S') => self.get_or_load("short", None), - Some('Z') => self.get_or_load("boolean", None), - Some('[') => self.get_or_load(component_name, None), - Some('L') => { - let class_name = component_name - .strip_prefix('L') - .and_then(|s| s.strip_suffix(';')) - .expect("invalid L descriptor"); - self.get_or_load(class_name, None) - } - None => Err(VmError::LoaderError( + if let Some(component_name) = class_name.strip_prefix('[') { + if component_name.is_empty() { + return 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 component = self.get_or_load(component_name, loader)?; let arr_class = self.create_array_class(component); return Ok(arr_class); } @@ -197,9 +206,11 @@ impl ClassLoader { let (module, class_fqn) = ("", what); let bytes = self .bimage - .get_class(module, class_fqn) - .or_else(|e| Self::load_class_from_disk(what)) - .map_err(|e1| VmError::LoaderError(format!("failed to find class: {}", what)))?; + .as_mut() + .and_then(|b| b.get_class(module, class_fqn).ok()) + .ok_or_else(|| "not in bimage") + .or_else(|_| Self::load_class_from_disk(what)) + .map_err(|_| VmError::LoaderError(format!("failed to find class: {}", what)))?; let (_, cf) = ClassFile::from_bytes((bytes.as_ref(), 0)).map_err(|e| VmError::DekuError(e))?; let runtime = self.runtime_class(cf); @@ -287,7 +298,13 @@ impl ClassLoader { if let Attribute::ConstantValue(val) = constant_pool.parse_attribute(x.clone()).unwrap() { - Some(val.into()) + match val { + Constant::Int(i) => Some(i.into()), + Constant::Long(l) => Some(l.into()), + Constant::Float(f) => Some(f.into()), + Constant::Double(d) => Some(d.into()), + Constant::String(_) => None, + } } else { None } @@ -371,6 +388,14 @@ impl ClassLoader { } }); + let bootstrap_attribute = + class_file.attributes.iter().find_map(|attr| { + match constant_pool.parse_attribute(attr.clone()).ok()? { + Attribute::BootstrapMethods(index) => Some(index), + _ => None, + } + }); + RuntimeClass { constant_pool, access_flags, @@ -386,6 +411,7 @@ impl ClassLoader { component_type: None, source_file, mirror_in_progress: Default::default(), + bootstrap_attribute, } } // pub fn get_or_create_array_class(class_name: &str) -> RuntimeClass { @@ -440,6 +466,7 @@ impl ClassLoader { super_interfaces: vec![cloneable, serializable], component_type: Some(component), // new field source_file: None, + bootstrap_attribute: None, }; let klass = Arc::new(klass); self.classes.insert((name.to_string(), None), klass.clone()); @@ -472,6 +499,7 @@ impl ClassLoader { super_interfaces: vec![], component_type: None, source_file: None, + bootstrap_attribute: None, }); self.classes.insert((name.to_string(), None), klass.clone()); diff --git a/crates/core/src/frame.rs b/crates/core/src/frame/frame.rs similarity index 74% rename from crates/core/src/frame.rs rename to crates/core/src/frame/frame.rs index e9f815f..aec5e72 100644 --- a/crates/core/src/frame.rs +++ b/crates/core/src/frame/frame.rs @@ -1,21 +1,25 @@ -use crate::attributes::{CodeAttribute, LineNumberTableEntry}; use crate::class::RuntimeClass; -use crate::class_file::constant_pool::{ConstantPoolExt, ConstantPoolGet}; +use crate::class_file::attributes::{CodeAttribute, LineNumberTableEntry}; +use crate::class_file::constant_pool::{ConstantPoolError, ConstantPoolExt, ConstantPoolGet}; use crate::class_file::{Bytecode, ConstantPoolEntry, MethodRef}; use crate::error::{StackTraceElement, VmError}; use crate::instructions::{Ops, WideData}; use crate::objects::ReferenceKind; use crate::objects::array::ArrayReference; use crate::prim::*; -use crate::value::{LocalVariables, OperandStack, Primitive, Value}; +use crate::value::{Primitive, Value}; use crate::vm::Vm; use crate::{ - BaseType, FieldType, VmThread, array_store, array_store_cast, binary_op, convert_float_to_int, - convert_int_narrow, convert_simple, error, float_cmp, if_int_cmp, if_int_zero, int_div_rem, - load, shift_op, store, unary_op, + BaseType, FieldType, MethodDescriptor, VmThread, array_store, array_store_cast, binary_op, + convert_float_to_int, convert_int_narrow, convert_simple, float_cmp, if_int_cmp, if_int_zero, + int_div_rem, load, shift_op, store, unary_op, }; + use deku::DekuContainerRead; -use log::{info, trace, warn}; +use log::{trace, warn}; + +use crate::frame::local_vars::LocalVariables; +use crate::frame::operand_stack::OperandStack; use std::fmt::{Display, Formatter}; use std::sync::Arc; @@ -95,10 +99,7 @@ impl Frame { table .iter() .rev() - .find(|entry| { - entry.start_pc as i64 <= self.pc - // (*start_pc as i64) <= self.pc) - }) + .find(|entry| entry.start_pc as i64 <= self.pc) .map(|entry| entry.line_number) } @@ -144,9 +145,9 @@ impl Frame { Ok(ExecutionResult::Return(())) => return Ok(None), Ok(ExecutionResult::ReturnValue(val)) => return Ok(Some(val)), Ok(ExecutionResult::Advance(offset)) => { - info!("pre offset: {}", self.pc); + trace!("pre offset: {}", self.pc); self.pc += offset as i64; - info!("post offset: {}", self.pc); + trace!("post offset: {}", self.pc); } Ok(_) => self.pc += 1, Err(x) => { @@ -178,12 +179,13 @@ impl Frame { self.bytecode .code .iter() - .find(|(offset, op)| *offset as i64 >= self.pc) + .find(|(offset, _op)| *offset as i64 >= self.pc) .map(|op| op.clone()) } } -enum ExecutionResult { +#[derive(Debug)] +pub(crate) enum ExecutionResult { Continue, Advance(i32), Return(()), @@ -194,7 +196,7 @@ impl Frame { fn pop(&mut self) -> Result { self.stack.pop() } - fn execute_instruction(&mut self, op: Ops) -> Result { + pub(crate) fn execute_instruction(&mut self, op: Ops) -> Result { match op { Ops::nop => { // TODO Should nop have any side effects? @@ -290,8 +292,6 @@ impl Frame { }; if let Some(x) = resolved { self.stack.push(x); - // on second thoughts, i dont think that's right - // self.stack.push(Value::Reference(None)); }; Ok(ExecutionResult::Continue) } @@ -540,7 +540,7 @@ impl Frame { Ops::fastore => array_store!(self, Float, Float), Ops::dastore => array_store!(self, Double, Double), Ops::aastore => { - let Value::Reference((value)) = self.pop()? else { + let Value::Reference(value) = self.pop()? else { panic!("Value on stack was not ref") }; let Value::Primitive(Primitive::Int(index)) = self.pop()? else { @@ -967,7 +967,7 @@ impl Frame { let x: i32 = match self.pop()? { Value::Primitive(Primitive::Int(v)) => v, Value::Primitive(Primitive::Boolean(v)) => { - if v { + if v == 1u8 { 1 } else { 0 @@ -985,7 +985,7 @@ impl Frame { match &self.method_ref.desc.return_type { Some(FieldType::Base(base_type)) => match base_type { - BaseType::Boolean => Ok(ExecutionResult::ReturnValue((x != 0).into())), + BaseType::Boolean => Ok(ExecutionResult::ReturnValue((x as u8).into())), BaseType::Byte => Ok(ExecutionResult::ReturnValue((x as i8).into())), BaseType::Char => Ok(ExecutionResult::ReturnValue((x as u16).into())), BaseType::Short => Ok(ExecutionResult::ReturnValue((x as i16).into())), @@ -1178,7 +1178,7 @@ impl Frame { let args = self.stack.pop_n(args_count)?; let refe = args .first() - .expect("Must have reciever") + .expect("Must have receiver") .as_ref_kind() .expect("Must be ref"); let class = refe.class(); @@ -1193,8 +1193,104 @@ impl Frame { Ok(ExecutionResult::Continue) } - Ops::invokedynamic(_, _) => { - todo!("invokeDynamic") + Ops::invokedynamic(index, _b) => { + debug_assert_eq!(_b, 0u16); + + // Check if already resolved (you'll want to cache this) + // For now, let's just resolve it every time + + let dyninfo = self.pool.get_invoke_dynamic_info(index)?; + let bootstrap_attr = self.class.bootstrap_attribute.as_ref().unwrap(); + let bsm = + &bootstrap_attr.bootstrap_methods[dyninfo.bootstrap_method_attr_index as usize]; + + let name_and_type = self + .pool + .get_name_and_type_info(dyninfo.name_and_type_index)?; + let call_site_name = self.pool.get_string(name_and_type.name_index)?; + let call_site_desc = self.pool.get_string(name_and_type.descriptor_index)?; + let call_site_method_type = MethodDescriptor::parse(&call_site_desc) + .map_err(ConstantPoolError::DescriptorParseError)?; + + // The bootstrap method handle reference + let bsm_handle = self.pool.get_method_handle_info(bsm.bootstrap_method_ref)?; + let kind = bsm_handle.reference_kind; + let heck = self.pool.resolve_method_ref(bsm_handle.reference_index)?; + println!("Bootstrap method handle: kind={:?}, ref={:?}", kind, heck); + // Resolve static arguments from constant pool + let static_args: Vec = Vec::new(); + for arg_index in &bsm.bootstrap_arguments { + let info = self.pool.get_string_info(*arg_index)?; + let string = self.pool.get_string(info.string_index)?; + println!("{}", string); + // static_args.push(arg); + } + + let class_objects = call_site_method_type + .parameters + .iter() + .map(|param| { + let name = param.as_class_name(); + let klass = self.thread.get_class(&name)?; + let obj = self.thread.gc.read().get(*klass.mirror.get().unwrap()); + Ok(Some(obj)) + }) + .collect::>, VmError>>()?; + let class_array_class = self.thread.get_class("[Ljava/lang/Class;")?; + + let ptypes = self + .thread + .gc + .write() + .new_object_array_from(class_array_class, class_objects.into_boxed_slice()); + let pval: Value = ptypes.clone().into(); + + println!("=================="); + println!("{}", pval); + println!("=================="); + let return_type = call_site_method_type.return_type.unwrap(); + let ret = return_type.as_class_name(); + let ret_klass = self.thread.get_class(&ret)?; + let rtype = self.thread.gc.read().get(*ret_klass.mirror.get().unwrap()); + let mt_cn_ref = MethodRef { + class: "java/lang/invoke/MethodType".to_string(), + name: "methodType".to_string(), + desc: MethodDescriptor::method_type(), + }; + + let mt = self + .thread + .invoke(mt_cn_ref, vec![rtype.into(), ptypes.into()])? + .unwrap(); + + // Pop runtime arguments from stack (based on call site descriptor) + let runtime_arg_count = call_site_method_type.parameters.len(); + let runtime_args = self.stack.pop_n(runtime_arg_count)?; + + // Now we need to: + // 1. Create a MethodHandles.Lookup for current class --- + // 2. Create a MethodType from call_site_desc + // 3. Invoke the bootstrap method with (Lookup, String name, MethodType, ...static_args) + // 4. Get back a CallSite + // 5. Extract the MethodHandle from CallSite.getTarget() + // 6. Invoke that MethodHandle with runtime_args + + //wait i think i can just call jvm methods here + let mhc = self.thread.get_class("java/lang/invoke/MethodHandles")?; + let refe = MethodRef { + class: "java/lang/invoke/MethodHandles".to_string(), + name: "lookup".to_string(), + desc: MethodDescriptor::lookup(), + }; + + let lookup = self.thread.invoke(refe, Vec::new())?.unwrap(); + // Full implementation requires MethodHandle invocation support + println!("{}", lookup); + todo!( + // "Full invokedynamic - bootstrap: {}::{}", + // bsm_handle.class_name, + // bsm_handle.method_name + ) } // can init class @@ -1216,11 +1312,11 @@ impl Frame { let Value::Primitive(Primitive::Int(count)) = value else { panic!("stack item was not int") }; - let array = self.thread.gc.write().new_primitive_array( - array_class, - array_type.clone(), - count, - ); + let array = self + .thread + .gc + .write() + .new_primitive_array(array_class, count); self.stack .push(Value::Reference(Some(ReferenceKind::ArrayReference(array)))); Ok(ExecutionResult::Continue) @@ -1246,7 +1342,7 @@ impl Frame { Ok(ExecutionResult::Continue) } Ops::arraylength => { - let Value::Reference(Some(ReferenceKind::ArrayReference((array)))) = + let Value::Reference(Some(ReferenceKind::ArrayReference(array))) = self.stack.pop().expect("value on stack") else { panic!("Reference not on stack or not an array") @@ -1488,3 +1584,314 @@ impl Frame { Ok(ExecutionResult::Continue) } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::class::{InitState, RuntimeClass}; + use crate::class_file::ClassFlags; + use crate::class_file::class_file::Bytecode; + use crate::vm::Vm; + use deku::DekuContainerRead; + use parking_lot::Mutex; + use std::sync::OnceLock; + use std::sync::atomic::AtomicBool; + + /// Create an empty Bytecode for testing using deku deserialization + fn empty_bytecode() -> Bytecode { + // Format: 4 bytes for length (u32 big endian) = 0, then no bytecode + let bytes: [u8; 4] = [0, 0, 0, 0]; + let (_rest, bytecode) = Bytecode::from_bytes((&bytes, 0)).unwrap(); + bytecode + } + + /// Creates a minimal RuntimeClass for testing + fn test_class() -> Arc { + Arc::new(RuntimeClass { + constant_pool: Arc::new(vec![]), + access_flags: ClassFlags::from(0u16), + this_class: "TestClass".to_string(), + super_class: None, + interfaces: vec![], + fields: vec![], + methods: vec![], + mirror: OnceLock::new(), + mirror_in_progress: AtomicBool::new(false), + init_state: Mutex::new(InitState::Initialized), + super_classes: vec![], + super_interfaces: vec![], + component_type: None, + source_file: None, + bootstrap_attribute: None, + }) + } + + /// Creates a minimal MethodRef for testing + fn test_method_ref() -> MethodRef { + MethodRef { + class: "TestClass".to_string(), + name: "testMethod".to_string(), + desc: MethodDescriptor { + parameters: vec![], + return_type: Some(FieldType::Base(BaseType::Int)), + }, + } + } + + /// Builder for creating test frames with controlled state + struct TestFrame { + vm: Arc, + stack: Vec, + locals: Vec, + max_locals: usize, + } + + impl TestFrame { + fn new() -> Self { + Self { + vm: Vm::new_for_test(), + stack: vec![], + locals: vec![], + max_locals: 10, + } + } + + fn with_stack(mut self, stack: Vec) -> Self { + self.stack = stack; + self + } + + fn with_locals(mut self, locals: Vec, max: usize) -> Self { + self.locals = locals; + self.max_locals = max; + self + } + + fn build(self) -> Frame { + let class = test_class(); + let thread = VmThread::current(&self.vm); + + let mut stack = OperandStack::with_capacity(100); + for val in self.stack { + stack.push(val); + } + + Frame { + pc: 0, + stack, + vars: LocalVariables::from_args(self.locals, self.max_locals), + pool: Arc::new(vec![]), + bytecode: empty_bytecode(), + thread, + method_ref: test_method_ref(), + class, + line_number_table: None, + } + } + } + + // Helper to extract int from stack top + fn pop_int(frame: &mut Frame) -> i32 { + match frame.pop().unwrap() { + Value::Primitive(Primitive::Int(i)) => i, + other => panic!("Expected int, got {:?}", other), + } + } + + fn pop_long(frame: &mut Frame) -> i64 { + match frame.pop().unwrap() { + Value::Primitive(Primitive::Long(l)) => l, + other => panic!("Expected long, got {:?}", other), + } + } + + // ==================== Constant Tests ==================== + + #[test] + fn test_iconst_0() { + let mut frame = TestFrame::new().build(); + let result = frame.execute_instruction(Ops::iconst_0); + assert!(matches!(result, Ok(ExecutionResult::Continue))); + assert_eq!(pop_int(&mut frame), 0); + } + + #[test] + fn test_iconst_m1() { + let mut frame = TestFrame::new().build(); + let result = frame.execute_instruction(Ops::iconst_m1); + assert!(matches!(result, Ok(ExecutionResult::Continue))); + assert_eq!(pop_int(&mut frame), -1); + } + + #[test] + fn test_bipush() { + let mut frame = TestFrame::new().build(); + let result = frame.execute_instruction(Ops::bipush(42)); + assert!(matches!(result, Ok(ExecutionResult::Continue))); + assert_eq!(pop_int(&mut frame), 42); + } + + #[test] + fn test_sipush() { + let mut frame = TestFrame::new().build(); + let result = frame.execute_instruction(Ops::sipush(1000)); + assert!(matches!(result, Ok(ExecutionResult::Continue))); + assert_eq!(pop_int(&mut frame), 1000); + } + + // ==================== Arithmetic Tests ==================== + + #[test] + fn test_iadd() { + let mut frame = TestFrame::new() + .with_stack(vec![10i32.into(), 20i32.into()]) + .build(); + let result = frame.execute_instruction(Ops::iadd); + assert!(matches!(result, Ok(ExecutionResult::Continue))); + assert_eq!(pop_int(&mut frame), 30); + } + + #[test] + fn test_isub() { + let mut frame = TestFrame::new() + .with_stack(vec![50i32.into(), 20i32.into()]) + .build(); + let result = frame.execute_instruction(Ops::isub); + assert!(matches!(result, Ok(ExecutionResult::Continue))); + assert_eq!(pop_int(&mut frame), 30); + } + + #[test] + fn test_imul() { + let mut frame = TestFrame::new() + .with_stack(vec![6i32.into(), 7i32.into()]) + .build(); + let result = frame.execute_instruction(Ops::imul); + assert!(matches!(result, Ok(ExecutionResult::Continue))); + assert_eq!(pop_int(&mut frame), 42); + } + + #[test] + fn test_idiv() { + let mut frame = TestFrame::new() + .with_stack(vec![100i32.into(), 10i32.into()]) + .build(); + let result = frame.execute_instruction(Ops::idiv); + assert!(matches!(result, Ok(ExecutionResult::Continue))); + assert_eq!(pop_int(&mut frame), 10); + } + + #[test] + fn test_ineg() { + let mut frame = TestFrame::new().with_stack(vec![42i32.into()]).build(); + let result = frame.execute_instruction(Ops::ineg); + assert!(matches!(result, Ok(ExecutionResult::Continue))); + assert_eq!(pop_int(&mut frame), -42); + } + + // ==================== Stack Manipulation Tests ==================== + + #[test] + fn test_dup() { + let mut frame = TestFrame::new().with_stack(vec![42i32.into()]).build(); + let result = frame.execute_instruction(Ops::dup); + assert!(matches!(result, Ok(ExecutionResult::Continue))); + assert_eq!(pop_int(&mut frame), 42); + assert_eq!(pop_int(&mut frame), 42); + } + + #[test] + fn test_swap() { + let mut frame = TestFrame::new() + .with_stack(vec![1i32.into(), 2i32.into()]) + .build(); + let result = frame.execute_instruction(Ops::swap); + assert!(matches!(result, Ok(ExecutionResult::Continue))); + assert_eq!(pop_int(&mut frame), 1); + assert_eq!(pop_int(&mut frame), 2); + } + + #[test] + fn test_pop() { + let mut frame = TestFrame::new() + .with_stack(vec![1i32.into(), 2i32.into()]) + .build(); + let result = frame.execute_instruction(Ops::pop); + assert!(matches!(result, Ok(ExecutionResult::Continue))); + assert_eq!(pop_int(&mut frame), 1); // only 1 should remain + } + + // ==================== Load/Store Tests ==================== + + #[test] + fn test_iload_0() { + let mut frame = TestFrame::new().with_locals(vec![99i32.into()], 4).build(); + let result = frame.execute_instruction(Ops::iload_0); + assert!(matches!(result, Ok(ExecutionResult::Continue))); + assert_eq!(pop_int(&mut frame), 99); + } + + #[test] + fn test_istore_0() { + let mut frame = TestFrame::new() + .with_stack(vec![42i32.into()]) + .with_locals(vec![0i32.into()], 4) + .build(); + let result = frame.execute_instruction(Ops::istore_0); + assert!(matches!(result, Ok(ExecutionResult::Continue))); + // Load it back to verify + let _ = frame.execute_instruction(Ops::iload_0); + assert_eq!(pop_int(&mut frame), 42); + } + + // ==================== Comparison/Branch Tests ==================== + + #[test] + fn test_ifeq_taken() { + let mut frame = TestFrame::new().with_stack(vec![0i32.into()]).build(); + let result = frame.execute_instruction(Ops::ifeq(10)); + assert!(matches!(result, Ok(ExecutionResult::Advance(10)))); + } + + #[test] + fn test_ifeq_not_taken() { + let mut frame = TestFrame::new().with_stack(vec![5i32.into()]).build(); + let result = frame.execute_instruction(Ops::ifeq(10)); + assert!(matches!(result, Ok(ExecutionResult::Continue))); + } + + #[test] + fn test_if_icmpeq_taken() { + let mut frame = TestFrame::new() + .with_stack(vec![5i32.into(), 5i32.into()]) + .build(); + let result = frame.execute_instruction(Ops::if_icmpeq(20)); + assert!(matches!(result, Ok(ExecutionResult::Advance(20)))); + } + + #[test] + fn test_goto() { + let mut frame = TestFrame::new().build(); + let result = frame.execute_instruction(Ops::goto(100)); + assert!(matches!(result, Ok(ExecutionResult::Advance(100)))); + } + + // ==================== Conversion Tests ==================== + + #[test] + fn test_i2l() { + let mut frame = TestFrame::new().with_stack(vec![42i32.into()]).build(); + let result = frame.execute_instruction(Ops::i2l); + assert!(matches!(result, Ok(ExecutionResult::Continue))); + assert_eq!(pop_long(&mut frame), 42i64); + } + + // ==================== Return Tests ==================== + + #[test] + fn test_return_void() { + let mut frame = TestFrame::new().build(); + let result = frame.execute_instruction(Ops::return_void); + assert!(matches!(result, Ok(ExecutionResult::Return(())))); + } +} diff --git a/crates/core/src/frame/local_vars.rs b/crates/core/src/frame/local_vars.rs new file mode 100644 index 0000000..4f18c30 --- /dev/null +++ b/crates/core/src/frame/local_vars.rs @@ -0,0 +1,115 @@ +use crate::value::Value; + +#[derive(Clone, Default)] +pub struct LocalVariables { + inner: Vec, +} + +impl LocalVariables { + pub fn with_capacity(max_locals: usize) -> Self { + Self { + inner: vec![Value::NULL; max_locals], + } + } + pub fn from_args(args: Vec, max_locals: usize) -> Self { + let mut locals = Self::with_capacity(max_locals); + let mut idx = 0; + for arg in args { + locals.set(idx, arg.clone()); + idx += if arg.is_wide() { 2 } else { 1 }; + } + locals + } + + pub fn set(&mut self, index: usize, value: Value) { + let idx = index; + let is_wide = value.is_wide(); + self.inner[idx] = value; + if is_wide { + self.inner[idx + 1] = Value::Padding; + } + } + + pub fn get(&self, index: usize) -> &Value { + let val = &self.inner[index]; + if matches!(val, Value::Padding) { + panic!( + "Attempted to read padding slot at index {} (second half of wide value at {})", + index, + index - 1 + ); + } + val + } + + pub fn iter(&self) -> impl Iterator { + self.inner.iter().filter(|v| !matches!(v, Value::Padding)) + } + + pub fn slots(&self) -> impl Iterator { + self.inner + .iter() + .enumerate() + .filter(|(_, v)| !matches!(v, Value::Padding)) + .map(|(i, v)| (i as u16, v)) + } +} + +impl std::fmt::Debug for LocalVariables { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_list() + .entries(self.slots().map(|(i, v)| format!("[{}]: {:?}", i, v))) + .finish() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::value::Primitive; + + #[test] + fn set_get_basic() { + let mut vars = LocalVariables::with_capacity(4); + vars.set(0, 42i32.into()); + let val = vars.get(0); + assert!(matches!(val, Value::Primitive(Primitive::Int(42)))); + } + + #[test] + fn wide_value_sets_padding() { + let mut vars = LocalVariables::with_capacity(4); + vars.set(0, 100i64.into()); // long is wide + + // Verify the long is at slot 0 + assert!(matches!(vars.get(0), Value::Primitive(Primitive::Long(100)))); + + // Slot 1 should be padding - accessing it will panic (tested separately) + // But we can verify slot 2 is still accessible (as NULL) + assert!(matches!(vars.get(2), Value::Reference(None))); + } + + #[test] + #[should_panic(expected = "padding slot")] + fn get_padding_slot_panics() { + let mut vars = LocalVariables::with_capacity(4); + vars.set(0, 100i64.into()); // Sets padding at index 1 + let _ = vars.get(1); // Should panic + } + + #[test] + fn from_args_spaces_wide_values() { + // Args: int, long, int -> should be at indices 0, 1, 3 + let args = vec![ + Value::from(1i32), + Value::from(2i64), // wide, takes slots 1-2 + Value::from(3i32), + ]; + let vars = LocalVariables::from_args(args, 5); + + let slots: Vec<_> = vars.slots().collect(); + assert_eq!(slots[0].0, 0); // first int at 0 + assert_eq!(slots[1].0, 1); // long at 1 + assert_eq!(slots[2].0, 3); // second int at 3 (skipped 2 for padding) + } +} diff --git a/crates/core/src/frame/mod.rs b/crates/core/src/frame/mod.rs new file mode 100644 index 0000000..3f5e412 --- /dev/null +++ b/crates/core/src/frame/mod.rs @@ -0,0 +1,5 @@ +pub mod frame; +mod local_vars; +mod operand_stack; + +pub use frame::*; diff --git a/crates/core/src/frame/operand_stack.rs b/crates/core/src/frame/operand_stack.rs new file mode 100644 index 0000000..d2cbbc9 --- /dev/null +++ b/crates/core/src/frame/operand_stack.rs @@ -0,0 +1,119 @@ +use crate::error::VmError; +use crate::value::Value; + +#[derive(Clone, Default)] +pub struct OperandStack(Vec); + +impl OperandStack { + pub fn new() -> Self { + Self(Vec::new()) + } + pub fn with_capacity(max_stack: usize) -> Self { + let backing = Vec::with_capacity(max_stack); + Self { 0: backing } + } + + pub fn push(&mut self, value: Value) { + self.0.push(value); + } + + pub fn pop(&mut self) -> Result { + self.0 + .pop() + .ok_or_else(|| VmError::StackError("Stack underflow".to_string())) + } + + pub fn peek(&self) -> Result<&Value, VmError> { + self.0 + .last() + .ok_or_else(|| VmError::StackError("Stack underflow on peek".to_string())) + } + + pub fn len(&self) -> usize { + self.0.len() + } + + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } + + /// Pop n values, returned in the order they were pushed (not pop order) + pub fn pop_n(&mut self, n: usize) -> Result, VmError> { + let start = self + .0 + .len() + .checked_sub(n) + .ok_or_else(|| VmError::StackError("Stack underflow on pop_n".to_string()))?; + Ok(self.0.drain(start..).collect()) + } +} + +impl std::fmt::Debug for OperandStack { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_list().entries(self.0.iter()).finish() + } +} + +impl OperandStack { + pub fn iter(&self) -> std::slice::Iter<'_, Value> { + self.into_iter() + } +} + +impl<'a> IntoIterator for &'a OperandStack { + type Item = &'a Value; + type IntoIter = std::slice::Iter<'a, Value>; + + fn into_iter(self) -> Self::IntoIter { + self.0.iter() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::value::Primitive; + + #[test] + fn push_pop_basic() { + let mut stack = OperandStack::new(); + stack.push(42i32.into()); + let val = stack.pop().unwrap(); + assert!(matches!(val, Value::Primitive(Primitive::Int(42)))); + } + + #[test] + fn pop_empty_returns_error() { + let mut stack = OperandStack::new(); + assert!(stack.pop().is_err()); + } + + #[test] + fn peek_does_not_remove() { + let mut stack = OperandStack::new(); + stack.push(1i32.into()); + let _ = stack.peek().unwrap(); + assert_eq!(stack.len(), 1); + } + + #[test] + fn pop_n_returns_push_order() { + let mut stack = OperandStack::new(); + stack.push(1i32.into()); + stack.push(2i32.into()); + stack.push(3i32.into()); + + let values = stack.pop_n(2).unwrap(); + // Should get [2, 3] not [3, 2] + assert!(matches!(values[0], Value::Primitive(Primitive::Int(2)))); + assert!(matches!(values[1], Value::Primitive(Primitive::Int(3)))); + assert_eq!(stack.len(), 1); + } + + #[test] + fn pop_n_underflow_returns_error() { + let mut stack = OperandStack::new(); + stack.push(1i32.into()); + assert!(stack.pop_n(5).is_err()); + } +} diff --git a/crates/core/src/instructions.rs b/crates/core/src/instructions.rs index 7d4338e..29fb58c 100644 --- a/crates/core/src/instructions.rs +++ b/crates/core/src/instructions.rs @@ -1,4 +1,4 @@ -use crate::attributes::{ArrayType, LookupSwitchData, TableSwitchData}; +use crate::class_file::attributes::{ArrayType, LookupSwitchData, TableSwitchData}; use deku_derive::DekuRead; use std::fmt::{Display, Formatter}; diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs index 4dafa4b..0fdc17a 100644 --- a/crates/core/src/lib.rs +++ b/crates/core/src/lib.rs @@ -16,22 +16,15 @@ //! - [`FieldType`] - Field type information use crate::class_file::MethodRef; -use crate::class_file::constant_pool::ConstantPoolExt; -use crate::class_file::constant_pool::ConstantPoolGet; pub use crate::thread::VmThread; -use deku::DekuContainerRead; use deku_derive::{DekuRead, DekuWrite}; -use frame::Frame; use itertools::Itertools; -use log::error; use std::borrow::Cow; use std::cell::RefCell; use std::fmt::{Debug, Display, Formatter}; -use std::io::Read; -use std::ops::{BitAnd, Deref}; +use std::str::FromStr; use value::Value; -mod attributes; mod bimage; mod class; pub mod class_file; @@ -99,6 +92,23 @@ pub enum BaseType { Boolean, } +impl FromStr for BaseType { + type Err = String; + + fn from_str(s: &str) -> Result { + match s { + "byte" => Ok(Self::Byte), + "char" => Ok(Self::Char), + "double" => Ok(Self::Double), + "float" => Ok(Self::Float), + "int" => Ok(Self::Int), + "long" => Ok(Self::Long), + "short" => Ok(Self::Short), + "boolean" => Ok(Self::Boolean), + s => Err(format!("Invalid primitive type name: {s}")), + } + } +} impl From for BaseType { fn from(value: char) -> Self { match value { @@ -136,9 +146,21 @@ impl MethodDescriptor { return_type: None, } } - pub fn psvm() -> Self { + pub fn method_type() -> Self { + MethodDescriptor::parse( + "(Ljava/lang/Class;[Ljava/lang/Class;)Ljava/lang/invoke/MethodType;", + ) + .unwrap() + } + pub fn lookup() -> Self { + MethodDescriptor::parse("()Ljava/lang/invoke/MethodHandles$Lookup;").unwrap() + } + pub fn psvmsa() -> Self { MethodDescriptor::parse("([Ljava/lang/String;)V").unwrap() } + pub fn psvm() -> Self { + MethodDescriptor::parse("()V").unwrap() + } pub fn param_string(&self) -> String { self.parameters.iter().join("") @@ -211,6 +233,12 @@ pub enum FieldType { ArrayType(Box), } +impl FieldType { + pub fn bool() -> Self { + Self::Base(BaseType::Boolean) + } +} + impl FieldType { pub fn default_value(&self) -> Value { match self { diff --git a/crates/core/src/macros.rs b/crates/core/src/macros.rs index a04a0ff..23e5f67 100644 --- a/crates/core/src/macros.rs +++ b/crates/core/src/macros.rs @@ -1,6 +1,3 @@ -use crate::class_file::constant_pool::ConstantPoolError; -use crate::value::Value; - #[macro_export] macro_rules! store { ($self:expr, l, $index:expr) => {{ diff --git a/crates/core/src/main.rs b/crates/core/src/main.rs index 317d833..584dee6 100644 --- a/crates/core/src/main.rs +++ b/crates/core/src/main.rs @@ -1,6 +1,6 @@ use colored::Colorize; use libloading::Library; -use log::{LevelFilter, error}; +use log::{LevelFilter, error, info}; use roast_vm_core::error::VmError; use roast_vm_core::vm::{LIB_RESOLVE_COUNTS, Vm}; use roast_vm_core::{get_last_native, stack_used}; @@ -17,21 +17,21 @@ fn main() { } fn run() { - // env_logger::Builder::from_default_env() - // .filter_level(LevelFilter::Trace) - // .filter_module("deku", LevelFilter::Warn) - // .filter_module("roast_vm_core::class_file::class_file", LevelFilter::Info) - // .filter_module("roast_vm_core::attributes", LevelFilter::Info) - // .filter_module("roast_vm_core::instructions", LevelFilter::Info) - // .init(); + env_logger::Builder::from_default_env() + .filter_level(LevelFilter::Trace) + .filter_module("deku", LevelFilter::Warn) + .filter_module("roast_vm_core::class_file::class_file", LevelFilter::Info) + .filter_module("roast_vm_core::class_file::attributes", LevelFilter::Info) + .filter_module("roast_vm_core::instructions", LevelFilter::Info) + .init(); stack_used(); - let mut vm = Vm::new(); + let vm = Vm::new(); fetch_libs(&vm); let start = Instant::now(); install_crash_handler(); - match vm.run("org/example/Main") { + match vm.run("com/infernap12/Main") { Ok(_) => { - println!("took {:?}", start.elapsed()); + info!("took {:?}", start.elapsed()); } Err(VmError::Exception { message, @@ -124,12 +124,7 @@ fn fetch_libs(vm: &Vm) { } fn load(path: PathBuf) -> Library { - let leeb = unsafe { Library::new(&path) }.expect("load dll"); - Library::from(leeb) -} - -fn linux_load(path: PathBuf) -> Library { - let leeb = unsafe { Library::new(&path) }.expect("load dll"); + let leeb = unsafe { Library::new(&path) }.expect("load lib"); Library::from(leeb) } @@ -153,11 +148,13 @@ fn format_bytes(bytes: usize) -> String { use windows::Win32::Foundation::EXCEPTION_ACCESS_VIOLATION; use windows::Win32::System::Diagnostics::Debug::*; unsafe extern "system" fn crash_handler(info: *mut EXCEPTION_POINTERS) -> i32 { - let record = (*info).ExceptionRecord; - if (*record).ExceptionCode == EXCEPTION_ACCESS_VIOLATION { - eprintln!("ACCESS VIOLATION during native call: {}", get_last_native()); + unsafe { + let record = (*info).ExceptionRecord; + if (*record).ExceptionCode == EXCEPTION_ACCESS_VIOLATION { + eprintln!("ACCESS VIOLATION during native call: {}", get_last_native()); + } + EXCEPTION_CONTINUE_SEARCH } - EXCEPTION_CONTINUE_SEARCH } // Call once at VM startup: diff --git a/crates/core/src/native/jni.rs b/crates/core/src/native/jni.rs index ebacd5d..3552e04 100644 --- a/crates/core/src/native/jni.rs +++ b/crates/core/src/native/jni.rs @@ -1,5 +1,7 @@ #![allow(unused_variables)] +#![allow(unused)] #![feature(c_variadic)] +#![allow(unsafe_op_in_unsafe_fn)] use crate::class::RuntimeClass; use crate::class_file::{FieldData, FieldRef}; use crate::objects::array::ArrayReference; @@ -8,11 +10,10 @@ use crate::prim::jboolean; use crate::thread::VmThread; use crate::value::{Primitive, Value}; use crate::{BaseType, FieldType, MethodDescriptor, generate_jni_short_name}; -use itertools::Itertools; use jni::sys::*; use jni::sys::{JNIEnv, jstring}; use jni::sys::{JNINativeInterface_, jclass, jint, jobject}; -use log::{error, info, trace, warn}; +use log::{debug, info, trace, warn}; use std::ffi::{CStr, CString, c_char}; use std::ptr; use std::sync::Arc; @@ -318,7 +319,7 @@ unsafe extern "system" fn get_string_utfchars( _ => return ptr::null(), }; - let mut obj = obj_ref.lock(); + let obj = obj_ref.lock(); let field_ref = FieldRef { class: "java/lang/String".to_string(), name: "value".to_string(), @@ -362,14 +363,19 @@ unsafe extern "system" fn get_object_class(env: *mut JNIEnv, obj: jobject) -> jc let obj_id = obj as u32; - let ReferenceKind::ObjectReference(obj_ref) = gc.get(obj_id) else { - return ptr::null_mut(); - }; + match gc.get(obj_id) { + ReferenceKind::ObjectReference(obj_ref) => { + let obj_lock = obj_ref.lock(); + let class: &Arc = &obj_lock.class; + *class.mirror.wait() as jclass + } + ReferenceKind::ArrayReference(arr_ref) => { + // Return the Class mirror for the array type - let obj_lock = obj_ref.lock(); - let class: &Arc = &obj_lock.class; - - *class.mirror.wait() as jclass + // however you get the array's class mirror + *arr_ref.class().mirror.wait() as jclass + } + } } unsafe extern "system" fn register_natives( @@ -402,7 +408,7 @@ unsafe extern "system" fn register_natives( } if full_name.contains("Java_java_lang_Class_desiredAssertionStatus0") { - warn!("MAJOR HACK, figure out what is wrong with desiredAssertionStatus0"); + // warn!("MAJOR HACK, figure out what is wrong with desiredAssertionStatus0"); continue; } @@ -430,7 +436,7 @@ unsafe extern "system" fn register_natives( "name:{name}, signature:{signature}, fn_ptr{}", fn_ptr.is_null() ); - println!("JNI register: {full_name}"); + debug!("JNI register: {full_name}"); } JNI_OK } @@ -1067,7 +1073,32 @@ unsafe extern "system" fn get_object_field( obj: jobject, field_id: jfieldID, ) -> jobject { - todo!("get_object_field") + let thread = &*get_thread(env); + + let field_key = thread + .vm + .safent + .read() + .resolve_field(field_id as jlong) + .expect("Invalid field ID"); + + let object_ref = thread + .gc + .read() + .get(obj as u32) + .try_into_object_reference() + .unwrap(); + + let guard = object_ref.lock(); + let value = guard.fields.get(&field_key.field_name); + + match value { + Some(ref entry) => match entry.value() { + Value::Reference(Some(ref_kind)) => ref_kind.id() as jobject, + _ => std::ptr::null_mut(), + }, + None => std::ptr::null_mut(), + } } unsafe extern "system" fn get_boolean_field( @@ -1085,8 +1116,8 @@ unsafe extern "system" fn get_boolean_field( desc: FieldType::Base(BaseType::Boolean), }; let val = object.lock().get_field(&field_ref); - if let Value::Primitive(Primitive::Boolean(bool)) = val { - if bool { JNI_TRUE } else { JNI_FALSE } + if let Value::Primitive(Primitive::Boolean(bol)) = val { + if bol == 1u8 { JNI_TRUE } else { JNI_FALSE } } else { JNI_FALSE } @@ -1462,8 +1493,8 @@ unsafe extern "system" fn get_static_boolean_field( let field_ref = &*(field_id as *const FieldData); let field_again = class.find_field(&field_ref.name, &field_ref.desc).unwrap(); let val = field_again.value.lock().clone(); - if let Some(Value::Primitive(Primitive::Boolean(bool))) = val { - if bool { JNI_TRUE } else { JNI_FALSE } + if let Some(Value::Primitive(Primitive::Boolean(bol))) = val { + if bol == JNI_TRUE { JNI_TRUE } else { JNI_FALSE } } else { JNI_FALSE } @@ -1717,7 +1748,15 @@ unsafe extern "system" fn release_string_utf_chars( } unsafe extern "system" fn get_array_length(env: *mut JNIEnv, array: jarray) -> jsize { - todo!("get_array_length") + let thread = &*get_thread(env); + let arr = thread + .gc + .read() + .get(array as u32) + .try_into_array_reference() + .unwrap(); + + arr.len() as jsize } unsafe extern "system" fn new_object_array( @@ -1963,7 +2002,19 @@ unsafe extern "system" fn get_byte_array_region( len: jsize, buf: *mut jbyte, ) { - todo!("get_byte_array_region") + let thread = &*get_thread(env); + let arr = thread + .gc + .read() + .get(array as u32) + .try_into_array_reference() + .unwrap(); + + if let ArrayReference::Byte(byte_arr) = arr { + let guard = byte_arr.lock(); + let slice = &guard.backing[start as usize..(start + len) as usize]; + std::ptr::copy_nonoverlapping(slice.as_ptr(), buf, len as usize); + } } unsafe extern "system" fn get_char_array_region( @@ -2221,7 +2272,7 @@ unsafe extern "system" fn delete_weak_global_ref(env: *mut JNIEnv, ref_: jweak) } unsafe extern "system" fn exception_check(env: *mut JNIEnv) -> jboolean { - warn!("exception_check"); + trace!("exception_check"); JNI_FALSE } diff --git a/crates/core/src/native/mod.rs b/crates/core/src/native/mod.rs index cd21ec6..701514e 100644 --- a/crates/core/src/native/mod.rs +++ b/crates/core/src/native/mod.rs @@ -1,3 +1,2 @@ pub mod jni; -mod native_libraries; -pub mod r#unsafe; \ No newline at end of file +pub mod r#unsafe; diff --git a/crates/core/src/native/native_libraries.rs b/crates/core/src/native/native_libraries.rs deleted file mode 100644 index 4465d0b..0000000 --- a/crates/core/src/native/native_libraries.rs +++ /dev/null @@ -1,14 +0,0 @@ -use crate::class_file::ConstantPoolEntry; - -use libloading::Library; -use std::collections::HashMap; - -// impl NativeExt for NativeLibraries {} -// -// trait NativeExt: AsRef<[..]> { -// fn find(&self, name: String) -> () { -// for lib in self.iter() { -// lib -// } -// } -// } diff --git a/crates/core/src/objects/object.rs b/crates/core/src/objects/object.rs index acb3fbe..b8a4960 100644 --- a/crates/core/src/objects/object.rs +++ b/crates/core/src/objects/object.rs @@ -4,7 +4,6 @@ use crate::error::VmError; use crate::objects::array::{ArrayReference, ObjectArrayReference, PrimitiveArrayReference}; use crate::prim::{jboolean, jbyte, jchar, jdouble, jfloat, jint, jlong, jshort}; use crate::value::Value; -use crate::{BaseType, FieldType}; use dashmap::DashMap; use log::trace; use parking_lot::Mutex; @@ -120,8 +119,8 @@ pub type Reference = Option; impl Display for ReferenceKind { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { let id = match self { - ReferenceKind::ObjectReference(x) => { - let guard = x.lock(); + ReferenceKind::ObjectReference(x) => unsafe { + let guard = x.make_guard_unchecked(); if guard.class.this_class == "java/lang/String" && let Some(field) = guard.fields.get("value") && let Value::Reference(Some(ReferenceKind::ArrayReference( @@ -134,7 +133,7 @@ impl Display for ReferenceKind { } else { format!("Obj<{}>", guard.class.this_class) } - } + }, ReferenceKind::ArrayReference(ArrayReference::Int(x)) => { let guard = x.lock(); let backing = &guard.backing; diff --git a/crates/core/src/objects/object_manager.rs b/crates/core/src/objects/object_manager.rs index bb640e2..13cc941 100644 --- a/crates/core/src/objects/object_manager.rs +++ b/crates/core/src/objects/object_manager.rs @@ -1,18 +1,15 @@ -use crate::attributes::ArrayType; use crate::class::RuntimeClass; -use crate::class_file::{ClassFlags, MethodData}; +use crate::class_file::ClassFlags; +use crate::class_file::attributes::ArrayType; use crate::error::VmError; -use crate::objects::array::{ - Array, ArrayReference, ArrayValue, ObjectArrayReference, PrimitiveArrayReference, -}; +use crate::objects::array::{Array, ArrayReference}; use crate::objects::object::{ Object, ObjectReference, Reference, ReferenceKind, string_from_bytes, }; -use crate::value::{Primitive, Value}; -use crate::{ThreadId, objects, rng}; +use crate::value::Value; +use crate::{BaseType, ThreadId}; use dashmap::DashMap; -use jni::sys::{jboolean, jbyte, jchar, jdouble, jfloat, jint, jlong, jshort}; -use log::warn; +use jni::sys::{jint, jlong}; use parking_lot::{Condvar, Mutex}; use std::collections::HashMap; use std::sync::Arc; @@ -78,41 +75,45 @@ impl ObjectManager { object } - pub fn new_primitive_array( - &mut self, - class: Arc, - array_type: ArrayType, - count: i32, - ) -> ArrayReference { + pub fn new_primitive_array(&mut self, class: Arc, count: i32) -> ArrayReference { let id = self.next_id(); assert!( !self.objects.contains_key(&id), "Generated ID already exists!" ); + let symbol = class + .this_class + .strip_prefix("[") + .unwrap() + .chars() + .next() + .unwrap(); + let array_type = symbol.into(); + let array_ref = match array_type { - ArrayType::T_INT => { + BaseType::Int => { ArrayReference::Int(Arc::new(Mutex::new(Array::new(id, class, count)))) } - ArrayType::T_BYTE => { + BaseType::Byte => { ArrayReference::Byte(Arc::new(Mutex::new(Array::new(id, class, count)))) } - ArrayType::T_SHORT => { + BaseType::Short => { ArrayReference::Short(Arc::new(Mutex::new(Array::new(id, class, count)))) } - ArrayType::T_LONG => { + BaseType::Long => { ArrayReference::Long(Arc::new(Mutex::new(Array::new(id, class, count)))) } - ArrayType::T_FLOAT => { + BaseType::Float => { ArrayReference::Float(Arc::new(Mutex::new(Array::new(id, class, count)))) } - ArrayType::T_DOUBLE => { + BaseType::Double => { ArrayReference::Double(Arc::new(Mutex::new(Array::new(id, class, count)))) } - ArrayType::T_CHAR => { + BaseType::Char => { ArrayReference::Char(Arc::new(Mutex::new(Array::new(id, class, count)))) } - ArrayType::T_BOOLEAN => { + BaseType::Boolean => { ArrayReference::Boolean(Arc::new(Mutex::new(Array::new(id, class, count)))) } }; @@ -158,7 +159,6 @@ impl ObjectManager { class: Arc, vector: Box<[i8]>, ) -> ArrayReference { - warn!("Manual sidechannel byte array creation"); let id = self.next_id(); assert!( !self.objects.contains_key(&id), @@ -198,7 +198,6 @@ impl ObjectManager { 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); let byte_vec = utf8 @@ -227,6 +226,9 @@ impl ObjectManager { self.strings.insert(key, id); jstr } + pub fn intern_existing_string(&mut self, utf: String, obj_id: u32) -> u32 { + *self.strings.entry(utf).or_insert(obj_id) + } pub fn new_class( &mut self, @@ -235,8 +237,14 @@ impl ObjectManager { module: Reference, modifiers: ClassFlags, primitive: bool, + component_type: Reference, ) -> ObjectReference { - warn!("Manual sidechannel class creation"); + let string = self + .transmute_string(name.clone().unwrap().try_into_object_reference().unwrap()) + .unwrap(); + if string.contains("byte") { + println!("!!!!!"); + } let module = None; let modifiers = 17u16; @@ -249,6 +257,7 @@ impl ObjectManager { clakz.set_field("modifiers", Value::from(modifiers)); clakz.set_field("primitive", Value::from(primitive)); clakz.set_field("classRedefinedCount", Value::from(class_redefined_count)); + clakz.set_field("componentType", Value::from(component_type)) } clazz } @@ -639,4 +648,56 @@ impl ObjectManager { } tg_object } + + pub fn new_method_type( + &mut self, + method_type_class: Arc, + class_array_class: Arc, + rtype: ReferenceKind, // Class mirror for return type + ptypes: Vec, // Class mirrors for param types + ) -> ObjectReference { + let ptypes_array = + self.new_object_array_from(class_array_class, ptypes.into_iter().map(Some).collect()); + let mt = self.new_object(method_type_class); + { + let obj = mt.lock(); + obj.set_field("rtype", Value::from(Some(rtype))); + obj.set_field("ptypes", Value::from(ptypes_array)); + } + mt + } + + pub fn new_member_name( + &mut self, + member_name_class: Arc, + clazz: ReferenceKind, // declaring class mirror + name: ObjectReference, // method/field name as String + method_type: ObjectReference, + flags: jint, // REF_invokeStatic, etc encoded + ) -> ObjectReference { + let mn = self.new_object(member_name_class); + { + let obj = mn.lock(); + obj.set_field("clazz", Value::from(Some(clazz))); + obj.set_field("name", Value::from(name)); + obj.set_field("type", Value::from(method_type)); + obj.set_field("flags", Value::from(flags)); + } + mn + } + + pub fn new_direct_method_handle( + &mut self, + dmh_class: Arc, + member: ObjectReference, + method_type: ObjectReference, + ) -> ObjectReference { + let mh = self.new_object(dmh_class); + { + let obj = mh.lock(); + obj.set_field("type", Value::from(method_type)); + obj.set_field("member", Value::from(member)); + } + mh + } } diff --git a/crates/core/src/rng.rs b/crates/core/src/rng.rs index e738dcc..f7598dd 100644 --- a/crates/core/src/rng.rs +++ b/crates/core/src/rng.rs @@ -1,3 +1,4 @@ +#[allow(unused)] use std::cell::Cell; use std::sync::atomic::{AtomicU32, Ordering}; diff --git a/crates/core/src/thread.rs b/crates/core/src/thread.rs index d9f15a1..fb2917b 100644 --- a/crates/core/src/thread.rs +++ b/crates/core/src/thread.rs @@ -1,5 +1,5 @@ -use crate::class::{InitState, RuntimeClass}; -use crate::class_file::{ClassFile, MethodData, MethodRef}; +use crate::class::{ClassRef, InitState, RuntimeClass}; +use crate::class_file::{MethodData, MethodRef}; use crate::class_loader::{ClassLoader, LoaderRef}; use crate::error::VmError; use crate::frame::Frame; @@ -10,26 +10,19 @@ use crate::value::{Primitive, Value}; use crate::vm::Vm; use crate::{ BaseType, FieldType, MethodDescriptor, ThreadId, generate_jni_method_name, set_last_native, - stack_used, }; -use deku::DekuError::Incomplete; -use itertools::Itertools; use jni::sys::{JNIEnv, jboolean, jbyte, jchar, jdouble, jfloat, jint, jlong, jobject, jshort}; -use libffi::low::call; use libffi::middle::*; -use log::{LevelFilter, info, trace, warn}; +use log::{LevelFilter, debug, trace, warn}; -use parking_lot::{Condvar, Mutex, Once, ReentrantMutex, RwLock}; +use parking_lot::{Condvar, Mutex, Once, RwLock}; use std::any::Any; use std::cell::RefCell; use std::collections::VecDeque; -use std::ops::{Add, Deref}; -use std::ptr::null_mut; -use std::sync::atomic::{AtomicU64, Ordering}; +use std::sync::atomic::Ordering; use std::sync::{Arc, OnceLock}; use std::thread; -use std::vec::IntoIter; static INIT_LOGGER: Once = Once::new(); type MethodCallResult = Result, VmError>; @@ -55,7 +48,7 @@ pub struct VmThread { impl VmThread { pub fn new(vm: Arc, loader: Option) -> Arc { - let id = ThreadId(vm.NEXT_ID.fetch_add(1, Ordering::SeqCst)); + let id = ThreadId(vm.next_id.fetch_add(1, Ordering::SeqCst)); let loader = loader.unwrap_or(vm.loader.clone()); let gc = vm.gc.clone(); @@ -141,7 +134,7 @@ impl VmThread { ); return Ok(()); } - InitState::Initializing(tid) => { + InitState::Initializing(_tid) => { // Different thread is initializing - in a real JVM we'd wait // For now, just return an error return Err(VmError::LoaderError(format!( @@ -203,9 +196,6 @@ impl VmThread { pub fn ensure_initialised(&self, class: &Arc) -> Result<(), VmError> { let current_thread = thread::current().id(); - if class.this_class.contains("Thread") { - println!("Yep"); - } { let mut state = class.init_state.lock(); @@ -256,7 +246,7 @@ impl VmThread { if let Some(method) = class.methods.iter().find(|m| m.name == "") { if class.this_class.contains("Thread") { - println!("executing init for: {}", class.this_class); + trace!("executing init for: {}", class.this_class); } self.execute_method(class, method, vec![])?; } @@ -285,12 +275,19 @@ impl VmThread { self.get_class("java/lang/Class")? }; let string = self.intern_string(&class.this_class); + let component_type = if (class.this_class.starts_with("[")) { + let component = self.get_class(class.this_class.strip_prefix("[").unwrap())?; + Some(self.gc.read().get(component.mirror())) + } else { + None + }; let class_obj = self.gc.write().new_class( class_class, Some(ReferenceKind::ObjectReference(string)), None, class.access_flags, false, + component_type, ); let id = class_obj.lock().id; class.mirror.set(id).expect("woops, id already set"); @@ -309,7 +306,7 @@ impl VmThread { } pub fn invoke(&self, method_reference: MethodRef, args: Vec) -> MethodCallResult { - if self.gc.read().objects.len() > 2387 { + if self.gc.read().objects.len() > 2350 { INIT_LOGGER.call_once(|| { env_logger::Builder::from_default_env() .filter_level(LevelFilter::Trace) @@ -340,19 +337,13 @@ impl VmThread { self.execute_method(&class, &method, args) } - pub fn invoke_native(&self, method: &MethodRef, mut args: Vec) -> MethodCallResult { + pub fn invoke_native(&self, method: &MethodRef, args: Vec) -> MethodCallResult { let symbol_name = generate_jni_method_name(method, false); - if symbol_name.contains("Java_java_lang_Thread_start0") { - // return Err(VmError::Debug( - // "RoastVM specific implementation required for Java_java_lang_Thread_start0", - // )); - println!() - } - - if symbol_name.contains("Java_java_lang_Class_desiredAssertionStatus0") { - warn!("MAJOR HACK, figure out what is wrong with desiredAssertionStatus0"); - return Ok(Some(Value::from(false))); + if symbol_name.contains("Java_java_lang_reflect_Array_newArray") { + return Err(VmError::Debug( + "RoastVM specific implementation required for Java_java_lang_reflect_Array_newArray", + )); } let result = unsafe { @@ -368,7 +359,7 @@ impl VmThread { )))?; // build pointer to native fn let cp = CodePtr::from_ptr(p); - + println!("invoke native fn: {}", symbol_name); let mut storage = Vec::new(); trace!("passing {} to native fn", Value::format_vec(&args)); let deq_args = VecDeque::from(args); @@ -433,7 +424,6 @@ impl VmThread { } } }; - warn!("Invoke native not final"); result } fn execute_method( @@ -442,8 +432,7 @@ impl VmThread { method: &MethodData, args: Vec, ) -> MethodCallResult { - info!("Executing {}", method.name.clone()); - warn!("Stack used: {}", stack_used()); + debug!("Executing {}", method.name.clone()); let method_ref = MethodRef { class: class.this_class.clone(), name: method.name.clone(), @@ -501,6 +490,20 @@ impl VmThread { // } // } // } + + pub fn new_object( + &self, + class: ClassRef, + desc: MethodDescriptor, + args: &[Value], + ) -> Result { + let obj = self.gc.write().new_object(class.clone()); + let method_ref = class.find_method("", &desc)?; + let mut args = args.to_vec(); + args.insert(0, obj.clone().into()); + let _ = self.invoke(method_ref.into(), args)?; + Ok(obj) + } } fn build_args<'a>( diff --git a/crates/core/src/value.rs b/crates/core/src/value.rs index acfed50..e11adf9 100644 --- a/crates/core/src/value.rs +++ b/crates/core/src/value.rs @@ -2,13 +2,10 @@ use crate::error::VmError; use crate::objects::array::ArrayReference; use crate::objects::object::{ObjectReference, ReferenceKind}; use crate::{BaseType, FieldType}; -use core::fmt; -use dashmap::DashMap; -use jni::sys::{jboolean, jbyte, jchar, jdouble, jfloat, jint, jlong, jshort}; +use jni::sys::{JNI_FALSE, JNI_TRUE, jboolean, jbyte, jchar, jdouble, jfloat, jint, jlong, jshort}; use log::trace; use std::fmt::{Display, Formatter}; use std::mem::{size_of, size_of_val}; -use std::ops::Deref; /// A reference-counted, thread-safe pointer to an Object. @@ -25,135 +22,6 @@ pub enum Value { Padding, } -#[derive(Clone, Default)] -pub struct OperandStack(Vec); - -impl OperandStack { - pub fn new() -> Self { - Self(Vec::new()) - } - pub fn with_capacity(max_stack: usize) -> Self { - let backing = Vec::with_capacity(max_stack); - Self { 0: backing } - } - - pub fn push(&mut self, value: Value) { - self.0.push(value); - } - - pub fn pop(&mut self) -> Result { - self.0 - .pop() - .ok_or_else(|| VmError::StackError("Stack underflow".to_string())) - } - - pub fn peek(&self) -> Result<&Value, VmError> { - self.0 - .last() - .ok_or_else(|| VmError::StackError("Stack underflow on peek".to_string())) - } - - pub fn len(&self) -> usize { - self.0.len() - } - - pub fn is_empty(&self) -> bool { - self.0.is_empty() - } - - /// Pop n values, returned in the order they were pushed (not pop order) - pub fn pop_n(&mut self, n: usize) -> Result, VmError> { - let start = self - .0 - .len() - .checked_sub(n) - .ok_or_else(|| VmError::StackError("Stack underflow on pop_n".to_string()))?; - Ok(self.0.drain(start..).collect()) - } -} -impl std::fmt::Debug for OperandStack { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_list().entries(self.0.iter()).finish() - } -} - -impl OperandStack { - pub fn iter(&self) -> std::slice::Iter<'_, Value> { - self.into_iter() - } -} - -impl<'a> IntoIterator for &'a OperandStack { - type Item = &'a Value; - type IntoIter = std::slice::Iter<'a, Value>; - - fn into_iter(self) -> Self::IntoIter { - self.0.iter() - } -} -#[derive(Clone, Default)] -pub struct LocalVariables { - inner: Vec, -} - -impl LocalVariables { - pub fn with_capacity(max_locals: usize) -> Self { - Self { - inner: vec![Value::NULL; max_locals], - } - } - pub fn from_args(args: Vec, max_locals: usize) -> Self { - let mut locals = Self::with_capacity(max_locals); - let mut idx = 0; - for arg in args { - locals.set(idx, arg.clone()); - idx += if arg.is_wide() { 2 } else { 1 }; - } - locals - } - - pub fn set(&mut self, index: usize, value: Value) { - let idx = index; - let is_wide = value.is_wide(); - self.inner[idx] = value; - if is_wide { - self.inner[idx + 1] = Value::Padding; - } - } - - pub fn get(&self, index: usize) -> &Value { - let val = &self.inner[index]; - if matches!(val, Value::Padding) { - panic!( - "Attempted to read padding slot at index {} (second half of wide value at {})", - index, - index - 1 - ); - } - val - } - - pub fn iter(&self) -> impl Iterator { - self.inner.iter().filter(|v| !matches!(v, Value::Padding)) - } - - pub fn slots(&self) -> impl Iterator { - self.inner - .iter() - .enumerate() - .filter(|(_, v)| !matches!(v, Value::Padding)) - .map(|(i, v)| (i as u16, v)) - } -} - -impl std::fmt::Debug for LocalVariables { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_list() - .entries(self.slots().map(|(i, v)| format!("[{}]: {:?}", i, v))) - .finish() - } -} - impl Value { pub const NULL: Value = Value::Reference(None); @@ -232,6 +100,16 @@ impl Value { ))), } } + + pub fn try_into_jboolean(self) -> Result { + match self { + Value::Primitive(Primitive::Boolean(z)) => Ok(z), + _ => Err(VmError::InvariantError(format!( + "Expected bool, found {}", + self.to_string() + ))), + } + } } macro_rules! impl_value_from { @@ -251,13 +129,13 @@ impl_value_from!(jbyte, Byte); impl_value_from!(jshort, Short); impl_value_from!(jchar, Char); -impl From for Value { - fn from(value: jboolean) -> Self { - Self::Primitive(Primitive::Boolean(value != 0)) +impl From for Value { + fn from(value: bool) -> Self { + Self::Primitive(Primitive::Boolean(if value { JNI_TRUE } else { JNI_FALSE })) } } -impl_value_from!(bool, Boolean); +impl_value_from!(jboolean, Boolean); impl From> for Value { fn from(value: Option) -> Self { @@ -284,7 +162,7 @@ impl From for Value { #[derive(Debug, Clone)] pub enum Primitive { /// Boolean value (true/false) - Boolean(bool), + Boolean(jboolean), /// Unicode character Char(u16), /// 32-bit floating point @@ -391,31 +269,31 @@ impl PartialEq for Value { } } ReferenceKind::ArrayReference(x) => match x { - ArrayReference::Int(x) => { + ArrayReference::Int(_x) => { matches!(other, FieldType::ArrayType(inner) if matches!(**inner, FieldType::Base(BaseType::Int))) } - ArrayReference::Byte(x) => { + ArrayReference::Byte(_x) => { matches!(other, FieldType::ArrayType(inner) if matches!(**inner, FieldType::Base(BaseType::Byte))) } - ArrayReference::Short(x) => { + ArrayReference::Short(_x) => { matches!(other, FieldType::ArrayType(inner) if matches!(**inner, FieldType::Base(BaseType::Short))) } - ArrayReference::Long(x) => { + ArrayReference::Long(_x) => { matches!(other, FieldType::ArrayType(inner) if matches!(**inner, FieldType::Base(BaseType::Long))) } - ArrayReference::Float(x) => { + ArrayReference::Float(_x) => { matches!(other, FieldType::ArrayType(inner) if matches!(**inner, FieldType::Base(BaseType::Float))) } - ArrayReference::Double(x) => { + ArrayReference::Double(_x) => { matches!(other, FieldType::ArrayType(inner) if matches!(**inner, FieldType::Base(BaseType::Double))) } - ArrayReference::Char(x) => { + ArrayReference::Char(_x) => { matches!(other, FieldType::ArrayType(inner) if matches!(**inner, FieldType::Base(BaseType::Char))) } - ArrayReference::Boolean(x) => { + ArrayReference::Boolean(_x) => { matches!(other, FieldType::ArrayType(inner) if matches!(**inner, FieldType::Base(BaseType::Boolean))) } - ArrayReference::Object(x) => { + ArrayReference::Object(_x) => { if let FieldType::ArrayType(inner) = other { if let FieldType::ClassType(_) = **inner { true diff --git a/crates/core/src/vm.rs b/crates/core/src/vm.rs index e8870f0..24c744b 100644 --- a/crates/core/src/vm.rs +++ b/crates/core/src/vm.rs @@ -1,11 +1,8 @@ -#[cfg(libloading_docs)] -use super::os::unix as imp; -use std::collections::HashMap; use std::ffi::c_void; // the implementation used here doesn't matter particularly much... -#[cfg(all(not(libloading_docs), unix))] +#[cfg(all(unix))] use libloading::os::unix as imp; -#[cfg(all(not(libloading_docs), windows))] +#[cfg(all(windows))] use libloading::os::windows as imp; use crate::class_file::{ClassFlags, MethodRef}; @@ -15,13 +12,15 @@ use crate::native::r#unsafe::UnsafeSupport; use crate::objects::object::ReferenceKind; use crate::objects::object_manager::ObjectManager; use crate::thread::VmThread; +use crate::value::Value; use crate::{MethodDescriptor, ThreadId}; use dashmap::DashMap; use imp::Library; -use log::trace; +use log::{info, trace}; use parking_lot::{Mutex, RwLock}; use std::sync::atomic::AtomicU64; use std::sync::{Arc, LazyLock}; +use std::time::Instant; type NativeLibraries = Arc>>; @@ -36,12 +35,38 @@ pub struct Vm { pub native_libraries: NativeLibraries, pub gc: Arc>, pub safent: RwLock, - pub NEXT_ID: AtomicU64, + pub next_id: AtomicU64, } impl Vm { // start vm, loading main from classfile pub fn new() -> Arc { + let vm = Arc::new(Self { + threads: DashMap::new(), + main_thread_id: ThreadId(0), + loader: Arc::new(Mutex::from(ClassLoader::with_bimage())), + native_methods: DashMap::new(), + native_libraries: Arc::new(RwLock::new(Vec::new())), + gc: Default::default(), + safent: Default::default(), + next_id: AtomicU64::new(0), + }); + + // Create main thread + let thread = VmThread::new(vm.clone(), None); + let thread_id = thread.id; + + // Store in VM + vm.threads.insert(thread_id, thread.clone()); + + // Set as current thread + VmThread::set_current(thread_id); + vm + } + + /// Creates a minimal VM for testing, without loading the boot image + #[cfg(test)] + pub(crate) fn new_for_test() -> Arc { let vm = Arc::new(Self { threads: DashMap::new(), main_thread_id: ThreadId(0), @@ -50,7 +75,7 @@ impl Vm { native_libraries: Arc::new(RwLock::new(Vec::new())), gc: Default::default(), safent: Default::default(), - NEXT_ID: AtomicU64::new(0), + next_id: AtomicU64::new(0), }); // Create main thread @@ -78,7 +103,7 @@ impl Vm { for (lib_name, lib) in self.native_libraries.read().iter() { let res = unsafe { lib.get::(name.as_bytes()) }; if res.is_ok() { - println!( + trace!( "Register native for native symbol: {} out of: {}", &name, lib_name ); @@ -127,7 +152,7 @@ impl Vm { for prim in prims { let klass = self.loader.lock().primitive_class(prim.0); - let class_class = thread.get_class("java/lang/Class")?; + let class_class = thread.loader.lock().get_or_load("java/lang/Class", None)?; let name_obj = thread.intern_string(&prim.0); let flags = ClassFlags::from(1041u16); let class_obj = self.gc.write().new_class( @@ -136,8 +161,9 @@ impl Vm { None, flags, true, + None, ); - klass.mirror.set(class_obj.lock().id).unwrap(); + // klass.mirror.set(class_obj.lock().id).unwrap(); let prim_array_klass = self.loader.lock().create_array_class(klass.clone()); let arr_name_obj = thread.intern_string(&prim_array_klass.this_class); @@ -147,6 +173,7 @@ impl Vm { None, flags, false, + Some(ReferenceKind::ObjectReference(class_obj)), ); prim_array_klass .mirror @@ -175,7 +202,11 @@ impl Vm { } pub fn run(&self, what: &str) -> Result<(), VmError> { + let start = Instant::now(); self.boot_strap()?; + info!("Bootstrap completed in {:?}", start.elapsed()); + let lt = self.loader.lock().access_time(); + info!("Module load time: {:?}", lt); // 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/src/CDS.rs b/crates/roast-vm-sys/src/CDS.rs index dec708e..0e0f57b 100644 --- a/crates/roast-vm-sys/src/CDS.rs +++ b/crates/roast-vm-sys/src/CDS.rs @@ -1,5 +1,4 @@ -use crate::get_thread; -use jni::sys::{jclass, jint, JNIEnv}; +use jni::sys::{JNIEnv, jclass, jint}; #[unsafe(no_mangle)] pub extern "C" fn Java_jdk_internal_misc_CDS_getCDSConfigStatus( diff --git a/crates/roast-vm-sys/src/class.rs b/crates/roast-vm-sys/src/class.rs index 3ac6ad0..249cc4c 100644 --- a/crates/roast-vm-sys/src/class.rs +++ b/crates/roast-vm-sys/src/class.rs @@ -1,11 +1,11 @@ use crate::{get_thread, resolve_object}; -use jni::sys::{JNI_TRUE, jboolean, jclass, jobject}; +use jni::sys::{JNI_FALSE, JNI_TRUE, jboolean, jclass, jobject}; use jni::sys::{JNIEnv, jint}; use jni::sys::{jobjectArray, jstring}; use log::trace; use roast_vm_core::class_file::FieldRef; +use roast_vm_core::objects::ReferenceKind; use roast_vm_core::objects::array::ArrayReference; -use roast_vm_core::objects::{Reference, ReferenceKind}; use roast_vm_core::value::Value; use roast_vm_core::{BaseType, FieldType}; use std::ptr; @@ -19,40 +19,13 @@ pub unsafe extern "system" fn Java_java_lang_Class_getPrimitiveClass<'local>( let thread = &*get_thread(env); let rust_string = { - let gc = thread.gc.read(); - let obj_id = name as u32; - - let obj_ref = match gc.get(obj_id) { + let obj_ref = match thread.gc.read().get(obj_id) { ReferenceKind::ObjectReference(obj) => obj, _ => return 0 as jclass, }; - let obj = obj_ref.lock(); - let field_ref = FieldRef { - class: "java/lang/String".to_string(), - name: "value".to_string(), - desc: FieldType::ArrayType(Box::new(FieldType::Base(BaseType::Byte))), - }; - let value_field = obj.get_field(&field_ref); - let Value::Reference(Some(ReferenceKind::ArrayReference(ArrayReference::Byte(byte_array)))) = - value_field - else { - return 0 as jclass; - }; - - let array = byte_array.lock(); - - let bytes: Vec = (&array).iter().map(|&b| b as u8).collect(); - - let mut utf16_chars = Vec::new(); - for chunk in bytes.chunks_exact(2) { - let code_unit = u16::from_le_bytes([chunk[0], chunk[1]]); - utf16_chars.push(code_unit); - } - - String::from_utf16_lossy(&utf16_chars) - // All locks dropped here at end of block + thread.gc.write().transmute_string(obj_ref).unwrap() }; let klass = thread.get_class(&rust_string).unwrap(); @@ -66,8 +39,8 @@ pub unsafe extern "system" fn Java_java_lang_Class_forName0( _class: jclass, name: jstring, initialize: jboolean, - loader: jobject, - caller: jclass, + _loader: jobject, + _caller: jclass, ) -> jclass { trace!("Java_java_lang_Class_forName0"); let thread = &*get_thread(env); @@ -152,3 +125,12 @@ pub unsafe extern "system" fn Java_java_lang_Class_getDeclaredConstructors0( output.id() as jobjectArray } + +#[unsafe(no_mangle)] +pub unsafe extern "system" fn Java_java_lang_Class_desiredAssertionStatus0( + _env: *mut JNIEnv, + _class: jclass, + _this: jclass, +) -> jboolean { + JNI_FALSE +} diff --git a/crates/roast-vm-sys/src/file_output_stream.rs b/crates/roast-vm-sys/src/file_output_stream.rs new file mode 100644 index 0000000..4720f30 --- /dev/null +++ b/crates/roast-vm-sys/src/file_output_stream.rs @@ -0,0 +1,102 @@ +use crate::get_thread; +use jni::sys::{JNIEnv, jboolean, jbyteArray, jint, jobject}; +use roast_vm_core::objects::array::ArrayReference; + +#[unsafe(no_mangle)] +unsafe extern "system" fn Java_java_io_FileOutputStream_writeBytes( + env: *mut JNIEnv, + this: jobject, + bytes: jbyteArray, + off: jint, + len: jint, + append: jboolean, +) { + let thread = &*get_thread(env); + + // Get the FileOutputStream's fd field (FileDescriptor) + let fos = thread + .gc + .read() + .get(this as u32) + .try_into_object_reference() + .unwrap(); + + let fd_obj_id = { + let guard = fos.lock(); + guard + .fields + .get("fd") + .and_then(|v| v.value().as_ref_kind().ok()) + .map(|r| r.id()) + .expect("FileOutputStream must have fd") + }; + + // Get the actual fd int from FileDescriptor + let fd_obj = thread + .gc + .read() + .get(fd_obj_id) + .try_into_object_reference() + .unwrap(); + + let fd = { + let guard = fd_obj.lock(); + guard + .fields + .get("fd") + .map(|v| v.value().clone().try_into_jint().unwrap()) + .expect("FileDescriptor must have fd") + }; + + // Get the byte array + let arr = thread + .gc + .read() + .get(bytes as u32) + .try_into_array_reference() + .unwrap(); + + if let ArrayReference::Byte(byte_arr) = arr { + let guard = byte_arr.lock(); + let slice = &guard.backing[off as usize..(off + len) as usize]; + let slice = slice.iter().map(|e| *e as u8).collect::>(); + + if fd == 1 { + use std::io::Write; + std::io::stdout().write_all(&slice).ok(); + } else if fd == 2 { + use std::io::Write; + std::io::stderr().write_all(&slice).ok(); + } + + // Write to file descriptor + #[cfg(windows)] + { + use std::io::Write; + use std::os::windows::io::FromRawHandle; + + let handle = fd as *mut std::ffi::c_void; + let mut file = std::fs::File::from_raw_handle(handle); + file.write_all(std::slice::from_raw_parts( + slice.as_ptr() as *const u8, + slice.len(), + )) + .ok(); + std::mem::forget(file); // Don't close the handle + } + + #[cfg(unix)] + { + use std::io::Write; + use std::os::unix::io::FromRawFd; + + let mut file = std::fs::File::from_raw_fd(fd); + file.write_all(std::slice::from_raw_parts( + slice.as_ptr() as *const u8, + slice.len(), + )) + .ok(); + std::mem::forget(file); // Don't close the fd + } + } +} diff --git a/crates/roast-vm-sys/src/lib.rs b/crates/roast-vm-sys/src/lib.rs index 7a57eb4..7ee3e6c 100644 --- a/crates/roast-vm-sys/src/lib.rs +++ b/crates/roast-vm-sys/src/lib.rs @@ -1,33 +1,34 @@ #![allow(non_snake_case)] - +#![allow(unsafe_op_in_unsafe_fn)] mod CDS; mod class; +mod file_output_stream; mod misc_unsafe; mod object; +mod reflect; mod reflection; mod runtime; mod scoped_memory_access; mod signal; +mod string; mod system; mod system_props; mod thread; -use jni::objects::{JClass, JObject, JString}; -use jni::strings::JNIString; -use jni::sys::{jclass, jlong, jobject, jobjectArray}; -use jni::{JNIEnv, NativeMethod}; +use jni::JNIEnv; +use jni::objects::{JClass, JString}; +use jni::sys::{jlong, jobject}; use roast_vm_core::VmThread; use roast_vm_core::objects::ReferenceKind; use roast_vm_core::objects::array::ArrayReference; use roast_vm_core::objects::object::ObjectReference; -use std::ffi::c_void; use std::io::Write; use std::time::{SystemTime, UNIX_EPOCH}; #[unsafe(no_mangle)] pub extern "system" fn Java_org_example_MockIO_print<'local>( - mut env: JNIEnv<'local>, - jclass: JClass<'local>, + env: JNIEnv<'local>, + _jclass: JClass<'local>, input: JString<'local>, ) { unsafe { @@ -44,8 +45,8 @@ pub extern "system" fn Java_org_example_MockIO_print<'local>( #[unsafe(no_mangle)] pub extern "system" fn Java_org_example_Main_getTime<'local>( - mut env: JNIEnv<'local>, - jclass: JClass<'local>, + _env: JNIEnv<'local>, + _jclass: JClass<'local>, ) -> jlong { SystemTime::now() .duration_since(UNIX_EPOCH) @@ -55,8 +56,8 @@ pub extern "system" fn Java_org_example_Main_getTime<'local>( #[unsafe(no_mangle)] pub extern "system" fn Java_org_example_MockIO_println<'local>( - mut env: JNIEnv<'local>, - jclass: JClass<'local>, + _env: JNIEnv<'local>, + _jclass: JClass<'local>, input: jlong, ) { // let input: String = env diff --git a/crates/roast-vm-sys/src/misc_unsafe.rs b/crates/roast-vm-sys/src/misc_unsafe.rs index 1fe2331..04f38d7 100644 --- a/crates/roast-vm-sys/src/misc_unsafe.rs +++ b/crates/roast-vm-sys/src/misc_unsafe.rs @@ -1,18 +1,16 @@ use crate::thread::THREAD_NEXT_ID_SENTINEL; -use crate::{get_thread, resolve_array, resolve_object}; +use crate::{get_thread, resolve_object}; use jni::sys::{JNI_FALSE, JNI_TRUE, JNIEnv, jboolean, jclass, jint, jlong, jobject, jstring}; use log::warn; -use roast_vm_core::native::r#unsafe::OffsetKind; use roast_vm_core::objects::ReferenceKind; use roast_vm_core::objects::array::ArrayReference; -use roast_vm_core::objects::object::string_from_bytes; use roast_vm_core::value::Value; use std::sync::atomic::Ordering; #[unsafe(no_mangle)] pub unsafe extern "system" fn Java_jdk_internal_misc_Unsafe_registerNatives( - env: *mut JNIEnv, - obj: jclass, + _env: *mut JNIEnv, + _obj: jclass, ) { //no op warn!("Unsafe_registerNatives is NO OP") @@ -151,7 +149,7 @@ pub unsafe extern "system" fn Java_jdk_internal_misc_Unsafe_compareAndSetReferen .read() .resolve_field(offset) .expect("unregistered field offset"); - let mut guard = obj_ref.lock(); + let guard = obj_ref.lock(); let current_id = guard .fields @@ -214,7 +212,7 @@ pub unsafe extern "system" fn Java_jdk_internal_misc_Unsafe_compareAndSetInt( .read() .resolve_field(offset) .expect("unregistered field offset"); - let mut guard = obj_ref.lock(); + let guard = obj_ref.lock(); let current = guard .fields .get(&key.field_name) @@ -243,7 +241,7 @@ pub unsafe extern "system" fn Java_jdk_internal_misc_Unsafe_compareAndSetLong( ) -> jboolean { let thread = &*get_thread(env); if offset == THREAD_NEXT_ID_SENTINEL { - return match thread.vm.NEXT_ID.compare_exchange( + return match thread.vm.next_id.compare_exchange( expected as u64, new_val as u64, Ordering::SeqCst, @@ -277,7 +275,7 @@ pub unsafe extern "system" fn Java_jdk_internal_misc_Unsafe_compareAndSetLong( .read() .resolve_field(offset) .expect("unregistered field offset"); - let mut guard = obj_ref.lock(); + let guard = obj_ref.lock(); let current = guard .fields .get(&key.field_name) @@ -328,7 +326,7 @@ pub unsafe extern "system" fn Java_jdk_internal_misc_Unsafe_compareAndExchangeIn .read() .resolve_field(offset) .expect("unregistered field offset"); - let mut guard = obj_ref.lock(); + let guard = obj_ref.lock(); let current = guard .fields .get(&key.field_name) @@ -377,7 +375,7 @@ pub unsafe extern "system" fn Java_jdk_internal_misc_Unsafe_compareAndExchangeLo .read() .resolve_field(offset) .expect("unregistered field offset"); - let mut guard = obj_ref.lock(); + let guard = obj_ref.lock(); let current = guard .fields .get(&key.field_name) @@ -440,7 +438,7 @@ pub unsafe extern "system" fn Java_jdk_internal_misc_Unsafe_compareAndExchangeRe .read() .resolve_field(offset) .expect("unregistered field offset"); - let mut guard = obj_ref.lock(); + let guard = obj_ref.lock(); let current = guard .fields @@ -594,7 +592,7 @@ pub unsafe extern "system" fn Java_jdk_internal_misc_Unsafe_getLongVolatile( ) -> jlong { let thread = &*get_thread(env); if obj.is_null() && offset == THREAD_NEXT_ID_SENTINEL { - return thread.vm.NEXT_ID.load(Ordering::SeqCst) as jlong; + return thread.vm.next_id.load(Ordering::SeqCst) as jlong; } let reference = thread.gc.read().get(obj as u32); diff --git a/crates/roast-vm-sys/src/object.rs b/crates/roast-vm-sys/src/object.rs index 3058651..eb0e8b5 100644 --- a/crates/roast-vm-sys/src/object.rs +++ b/crates/roast-vm-sys/src/object.rs @@ -3,7 +3,7 @@ use jni::sys::{JNIEnv, jint, jlong, jobject}; #[unsafe(no_mangle)] pub unsafe extern "system" fn Java_java_lang_Object_hashCode( - env: *mut JNIEnv, + _env: *mut JNIEnv, obj: jobject, ) -> jint { obj as u32 as i32 diff --git a/crates/roast-vm-sys/src/reflect/array.rs b/crates/roast-vm-sys/src/reflect/array.rs new file mode 100644 index 0000000..487dd38 --- /dev/null +++ b/crates/roast-vm-sys/src/reflect/array.rs @@ -0,0 +1,38 @@ +use crate::get_thread; +use jni::sys::{JNI_TRUE, JNIEnv, jclass, jint, jobject}; +use roast_vm_core::FieldType; +use roast_vm_core::class_file::FieldRef; + +#[unsafe(no_mangle)] +pub unsafe extern "system" fn Java_java_lang_reflect_Array_newArray( + env: *mut JNIEnv, + _class: jclass, + component_type: jclass, + length: jint, +) -> jobject { + let thread = &*get_thread(env); + let comp = thread + .loader + .lock() + .class_from_mirror_id(component_type as u32) + .unwrap(); + let classObj = thread + .gc + .read() + .get(component_type as u32) + .try_into_object_reference() + .unwrap(); + + let prim = classObj + .lock() + .get_field(&FieldRef::new("Class", "primitive", FieldType::bool())) + .try_into_jboolean() + .unwrap(); + + let refe = if prim == JNI_TRUE { + thread.gc.write().new_primitive_array(comp, length) + } else { + thread.gc.write().new_object_array(comp, length) + }; + refe.id() as jobject +} diff --git a/crates/roast-vm-sys/src/reflect/mod.rs b/crates/roast-vm-sys/src/reflect/mod.rs new file mode 100644 index 0000000..f5d68fc --- /dev/null +++ b/crates/roast-vm-sys/src/reflect/mod.rs @@ -0,0 +1 @@ +pub mod array; diff --git a/crates/roast-vm-sys/src/reflection.rs b/crates/roast-vm-sys/src/reflection.rs index f2757b8..852d47b 100644 --- a/crates/roast-vm-sys/src/reflection.rs +++ b/crates/roast-vm-sys/src/reflection.rs @@ -1,9 +1,9 @@ use crate::get_thread; -use jni::sys::{jboolean, jclass, jint, jobject, jobjectArray, JNIEnv}; +use jni::sys::{JNIEnv, jclass, jint, jobject, jobjectArray}; use roast_vm_core::class_file::FieldRef; use roast_vm_core::objects::array::ArrayReference; use roast_vm_core::value::Value; -use roast_vm_core::{BaseType, FieldType, MethodDescriptor}; +use roast_vm_core::{BaseType, FieldType}; #[unsafe(no_mangle)] pub unsafe extern "system" fn Java_jdk_internal_reflect_Reflection_getCallerClass( @@ -21,7 +21,7 @@ pub unsafe extern "system" fn Java_jdk_internal_reflect_Reflection_getCallerClas #[unsafe(no_mangle)] pub unsafe extern "system" fn Java_jdk_internal_reflect_Reflection_getClassAccessFlags( env: *mut JNIEnv, - reflection_class: jclass, + _reflection_class: jclass, class: jclass, ) -> jint { let thread = &*get_thread(env); @@ -38,7 +38,7 @@ pub unsafe extern "system" fn Java_jdk_internal_reflect_Reflection_getClassAcces #[unsafe(no_mangle)] pub unsafe extern "system" fn Java_jdk_internal_reflect_DirectConstructorHandleAccessor_00024NativeAccessor_newInstance0( env: *mut JNIEnv, - DirectConstructorHandleAccessor_class: jclass, + _DirectConstructorHandleAccessor_class: jclass, constructor: jobject, args: jobjectArray, ) -> jobject { diff --git a/crates/roast-vm-sys/src/scoped_memory_access.rs b/crates/roast-vm-sys/src/scoped_memory_access.rs index 4d3ddcf..96f66c4 100644 --- a/crates/roast-vm-sys/src/scoped_memory_access.rs +++ b/crates/roast-vm-sys/src/scoped_memory_access.rs @@ -2,7 +2,7 @@ use jni::sys::{JNIEnv, jclass}; #[unsafe(no_mangle)] pub unsafe extern "system" fn Java_jdk_internal_misc_ScopedMemoryAccess_registerNatives( - env: *mut JNIEnv, + _env: *mut JNIEnv, _class: jclass, ) { // noop diff --git a/crates/roast-vm-sys/src/string.rs b/crates/roast-vm-sys/src/string.rs new file mode 100644 index 0000000..bfbfdfa --- /dev/null +++ b/crates/roast-vm-sys/src/string.rs @@ -0,0 +1,21 @@ +use crate::get_thread; +use jni::sys::{JNIEnv, jstring}; + +#[unsafe(no_mangle)] +unsafe extern "system" fn Java_java_lang_String_intern(env: *mut JNIEnv, this: jstring) -> jstring { + let thread = &*get_thread(env); + let obj_id = this as u32; + + let string_ref = thread + .gc + .read() + .get(obj_id) + .try_into_object_reference() + .unwrap(); + + let utf = thread.gc.read().transmute_string(string_ref).unwrap(); + + let interned_id = thread.gc.write().intern_existing_string(utf, obj_id); + + interned_id as jstring +} diff --git a/crates/roast-vm-sys/src/system.rs b/crates/roast-vm-sys/src/system.rs index 764b1fe..270ebcf 100644 --- a/crates/roast-vm-sys/src/system.rs +++ b/crates/roast-vm-sys/src/system.rs @@ -1,7 +1,6 @@ -use crate::{get_thread, resolve_array, resolve_object}; +use crate::{get_thread, resolve_array}; use jni::objects::JClass; use jni::sys::{JNIEnv, jclass, jint, jlong, jobject}; -use log::warn; use std::time::{SystemTime, UNIX_EPOCH}; #[unsafe(no_mangle)] @@ -50,7 +49,6 @@ pub unsafe extern "system" fn Java_java_lang_System_arraycopy( { panic!("Array index out of bounds!"); // throw_exception(env, "java/lang/ArrayIndexOutOfBoundsException", None); - return; } dst_arr @@ -61,8 +59,8 @@ pub unsafe extern "system" fn Java_java_lang_System_arraycopy( #[unsafe(no_mangle)] pub extern "system" fn current_time_millis<'local>( - mut env: jni::JNIEnv<'local>, - jclass: JClass<'local>, + _env: jni::JNIEnv<'local>, + _jclass: JClass<'local>, ) -> jlong { SystemTime::now() .duration_since(UNIX_EPOCH) @@ -72,8 +70,8 @@ pub extern "system" fn current_time_millis<'local>( #[unsafe(no_mangle)] pub extern "system" fn Java_java_lang_System_nanoTime<'local>( - mut env: jni::JNIEnv<'local>, - jclass: JClass<'local>, + _env: jni::JNIEnv<'local>, + _jclass: JClass<'local>, ) -> jlong { SystemTime::now() .duration_since(UNIX_EPOCH) @@ -83,8 +81,8 @@ pub extern "system" fn Java_java_lang_System_nanoTime<'local>( #[unsafe(no_mangle)] pub extern "system" fn Java_java_lang_System_registerNatives<'local>( - mut env: jni::JNIEnv<'local>, - jclass: JClass<'local>, + _env: jni::JNIEnv<'local>, + _jclass: JClass<'local>, ) { // No-op: native methods are handled by roast } diff --git a/crates/roast-vm-sys/src/thread.rs b/crates/roast-vm-sys/src/thread.rs index fcf9b64..1158f6e 100644 --- a/crates/roast-vm-sys/src/thread.rs +++ b/crates/roast-vm-sys/src/thread.rs @@ -58,7 +58,7 @@ unsafe extern "system" fn Java_java_lang_Thread_start0(env: *mut JNIEnv, this: j VmThread::set_current(thread_id); // Call this.run() - let thread_class = new_thread.get_class("java/lang/Thread").unwrap(); + // let thread_class = new_thread.get_class("java/lang/Thread").unwrap(); let run_ref = MethodRef { class: "java/lang/Thread".to_string(), name: "run".to_string(),