diff --git a/Cargo.lock b/Cargo.lock index 4f831393..3f88d3c9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -102,6 +102,16 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" +[[package]] +name = "ctor" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edb49164822f3ee45b17acd4a208cfc1251410cf0cad9a833234c9890774dd9f" +dependencies = [ + "quote", + "syn", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.1" @@ -130,11 +140,30 @@ dependencies = [ "autocfg", ] +[[package]] +name = "proc-macro2" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + [[package]] name = "rusty-jvm" version = "0.1.0" dependencies = [ "clap", + "ctor", "vm", ] @@ -144,6 +173,23 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "syn" +version = "2.0.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" + [[package]] name = "utf8parse" version = "0.2.2" diff --git a/Cargo.toml b/Cargo.toml index df8c4ddc..11550504 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,3 +10,5 @@ members = ["jclass", "jdescriptor", "vm"] clap = "4.5.16" vm = {path = "vm"} +[dev-dependencies] +ctor = "0.2.8" diff --git a/jclass/src/attributes.rs b/jclass/src/attributes.rs index d3e9a4de..4f8c2d11 100644 --- a/jclass/src/attributes.rs +++ b/jclass/src/attributes.rs @@ -13,7 +13,7 @@ use crate::extractors::{get_bitfield, get_bytes, get_int}; use bitflags::bitflags; use std::io::ErrorKind::InvalidData; -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Clone)] pub enum Attribute { ConstantValue { constantvalue_index: u16, @@ -92,7 +92,7 @@ pub enum Attribute { }, } -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Clone)] pub struct ExceptionRecord { start_pc: u16, end_pc: u16, @@ -123,7 +123,7 @@ impl ExceptionRecord { } } -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Clone)] pub struct LineNumberRecord { start_pc: u16, line_number: u16, @@ -144,7 +144,7 @@ impl LineNumberRecord { } } -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Clone)] pub struct LocalVariableTableRecord { start_pc: u16, length: u16, @@ -186,7 +186,7 @@ impl LocalVariableTableRecord { } } -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Clone)] pub struct LocalVariableTypeTableRecord { start_pc: u16, length: u16, @@ -229,7 +229,7 @@ impl LocalVariableTypeTableRecord { } bitflags! { - #[derive(Debug, PartialEq)] + #[derive(Debug, PartialEq, Clone)] pub struct MethodParameterFlags: u16 { const ACC_FINAL = 0x0010; // Indicates that the formal parameter was declared final. const ACC_SYNTHETIC = 0x1000; // Indicates that the formal parameter was not explicitly or implicitly declared in source code, according to the specification of the language in which the source code was written (JLS ยง13.1). (The formal parameter is an implementation artifact of the compiler which produced this class file.) @@ -237,7 +237,7 @@ bitflags! { } } -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Clone)] pub struct MethodParameterRecord { name_index: u16, access_flags: MethodParameterFlags, @@ -258,7 +258,7 @@ impl MethodParameterRecord { } } -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Clone)] pub enum StackMapFrame { SameFrame { frame_type: u8, @@ -295,7 +295,7 @@ pub enum StackMapFrame { }, } -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Clone)] pub enum VerificationTypeInfo { TopVariableInfo, IntegerVariableInfo, @@ -308,7 +308,7 @@ pub enum VerificationTypeInfo { UninitializedVariableInfo { offset: u16 }, } -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Clone)] pub struct Annotation { type_index: u16, element_value_pairs: Vec, @@ -329,7 +329,7 @@ impl Annotation { } } -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Clone)] pub struct ElementValuePair { element_name_index: u16, value: ElementValue, @@ -350,7 +350,7 @@ impl ElementValuePair { } } -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Clone)] pub enum ElementValue { ConstValueIndex { tag: u8, @@ -376,7 +376,7 @@ pub enum ElementValue { } bitflags! { - #[derive(Debug, PartialEq)] + #[derive(Debug, PartialEq, Clone)] pub struct NestedClassFlags: u16 { const ACC_PUBLIC = 0x0001; // Marked or implicitly public in source. const ACC_PRIVATE = 0x0002; // Marked private in source. @@ -391,7 +391,7 @@ bitflags! { } } -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Clone)] pub struct InnerClassRecord { inner_class_info_index: u16, outer_class_info_index: u16, @@ -427,7 +427,7 @@ impl InnerClassRecord { } } -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Clone)] pub struct BootstrapMethodRecord { bootstrap_method_ref: u16, bootstrap_arguments: Vec, @@ -448,7 +448,7 @@ impl BootstrapMethodRecord { } } -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Clone)] pub struct RecordComponentInfo { name_index: u16, descriptor_index: u16, diff --git a/jclass/src/constant_pool.rs b/jclass/src/constant_pool.rs index 49cf981d..4a8055a5 100644 --- a/jclass/src/constant_pool.rs +++ b/jclass/src/constant_pool.rs @@ -4,7 +4,7 @@ use crate::extractors::{get_float, get_int, get_string}; use std::io::ErrorKind::InvalidInput; #[repr(u8)] -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Clone)] pub enum ConstantPool { Empty = 0, Utf8 { diff --git a/jdescriptor/src/lib.rs b/jdescriptor/src/lib.rs index ea2052d3..d5817a73 100644 --- a/jdescriptor/src/lib.rs +++ b/jdescriptor/src/lib.rs @@ -12,7 +12,7 @@ enum DescriptorError { TooManyDimensions, } -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Clone)] pub enum TypeDescriptor { Byte, Char, diff --git a/src/main.rs b/src/main.rs index b42596cf..f98627cf 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,4 @@ -use clap::{arg, Arg, ArgAction, Command}; +use clap::{arg, Command}; use std::process; use vm::vm::VM; @@ -14,29 +14,15 @@ fn main() { .help("Class to run") .required(true), ) - .arg( - Arg::new("classes") - .action(ArgAction::Append) - .help("Java classes to load") - .required(true), - ) .get_matches(); - let std_dir = matches.get_one::("std-dir").unwrap(); - let entry_point = matches.get_one::("entry-point").unwrap(); - let classes = matches - .get_many::("classes") - .unwrap() - .into_iter() - .map(|s| s.as_str()) - .collect(); - let vm = match VM::new(classes, std_dir) { - Ok(vm) => vm, - Err(err) => { - eprintln!("Failed to create VM: {}", err); - process::exit(1); - } - }; + let std_dir = matches + .get_one::("std-dir") + .expect("Missing standard library directory"); + let entry_point = matches + .get_one::("entry-point") + .expect("Missing entry point"); + let mut vm = VM::new(std_dir); let result = match vm.run(entry_point) { Ok(output) => output, diff --git a/tests/integration_test.rs b/tests/integration_test.rs index 6eeb8a54..5579278e 100644 --- a/tests/integration_test.rs +++ b/tests/integration_test.rs @@ -1,34 +1,36 @@ +use ctor::ctor; +use std::env; +use std::sync::Once; use vm::vm::VM; +static INIT: Once = Once::new(); + +const PATH: &str = "tests/test_data"; + +#[ctor] +fn setup() { + INIT.call_once(|| { + env::set_current_dir(PATH).expect("Failed to change working directory"); + }) +} + #[test] fn should_do_adding() { - let vm = VM::new( - vec!["tests/test_data/samples/arithmetics/adder/ints/AdderInt.class"], - "tests/test_data/std", - ) - .unwrap(); + let mut vm = VM::new("std"); let last_frame_value = vm.run("samples.arithmetics.adder.ints.AdderInt").unwrap(); assert_eq!(55, get_int(last_frame_value)) } #[test] fn should_do_adding_with_longs() { - let vm = VM::new( - vec!["tests/test_data/samples/arithmetics/adder/longs/AdderLong.class"], - "tests/test_data/std", - ) - .unwrap(); + let mut vm = VM::new("std"); let last_frame_value = vm.run("samples.arithmetics.adder.longs.AdderLong").unwrap(); assert_eq!(171798691900, get_long(last_frame_value)) } #[test] fn should_do_adding_with_negative_longs() { - let vm = VM::new( - vec!["tests/test_data/samples/arithmetics/addernegative/AdderNegativeLong.class"], - "tests/test_data/std", - ) - .unwrap(); + let mut vm = VM::new("std"); let last_frame_value = vm .run("samples.arithmetics.addernegative.AdderNegativeLong") .unwrap(); @@ -37,36 +39,21 @@ fn should_do_adding_with_negative_longs() { #[test] fn should_do_subtraction() { - let vm = VM::new( - vec!["tests/test_data/samples/arithmetics/sub/ints/SubInts.class"], - "tests/test_data/std", - ) - .unwrap(); + let mut vm = VM::new("std"); let last_frame_value = vm.run("samples.arithmetics.sub.ints.SubInts").unwrap(); assert_eq!(-999, get_int(last_frame_value)) } #[test] fn should_do_subtraction_with_longs() { - let vm = VM::new( - vec!["tests/test_data/samples/arithmetics/sub/longs/SubLongs.class"], - "tests/test_data/std", - ) - .unwrap(); + let mut vm = VM::new("std"); let last_frame_value = vm.run("samples.arithmetics.sub.longs.SubLongs").unwrap(); assert_eq!(-1_000_000_000, get_long(last_frame_value)) } #[test] fn should_write_read_instance_fields() { - let vm = VM::new( - vec![ - "tests/test_data/samples/fields/instance/ints/InstanceFieldsUserInts.class", - "tests/test_data/samples/fields/instance/ints/InstanceFields.class", - ], - "tests/test_data/std", - ) - .unwrap(); + let mut vm = VM::new("std"); let last_frame_value = vm .run("samples.fields.instance.ints.InstanceFieldsUserInts") .unwrap(); @@ -75,14 +62,7 @@ fn should_write_read_instance_fields() { #[test] fn should_write_read_instance_fields_with_longs() { - let vm = VM::new( - vec![ - "tests/test_data/samples/fields/instance/longs/InstanceFieldsUserLong.class", - "tests/test_data/samples/fields/instance/longs/InstanceFields.class", - ], - "tests/test_data/std", - ) - .unwrap(); + let mut vm = VM::new("std"); let last_frame_value = vm .run("samples.fields.instance.longs.InstanceFieldsUserLong") .unwrap(); @@ -91,14 +71,7 @@ fn should_write_read_instance_fields_with_longs() { #[test] fn should_write_read_static_fields() { - let vm = VM::new( - vec![ - "tests/test_data/samples/fields/staticinitialization/ints/StaticFieldsUserInts.class", - "tests/test_data/samples/fields/staticinitialization/ints/StaticFields.class", - ], - "tests/test_data/std", - ) - .unwrap(); + let mut vm = VM::new("std"); let last_frame_value = vm .run("samples.fields.staticinitialization.ints.StaticFieldsUserInts") .unwrap(); @@ -107,11 +80,7 @@ fn should_write_read_static_fields() { #[test] fn should_do_extreme_stack_operations() { - let vm = VM::new( - vec!["tests/test_data/samples/arithmetics/extremestack/ints/ExtremeStackInt.class"], - "tests/test_data/std", - ) - .unwrap(); + let mut vm = VM::new("std"); let last_frame_value = vm .run("samples.arithmetics.extremestack.ints.ExtremeStackInt") .unwrap(); @@ -120,11 +89,7 @@ fn should_do_extreme_stack_operations() { #[test] fn should_do_extreme_stack_operations_with_longs() { - let vm = VM::new( - vec!["tests/test_data/samples/arithmetics/extremestack/longs/ExtremeStackLong.class"], - "tests/test_data/std", - ) - .unwrap(); + let mut vm = VM::new("std"); let last_frame_value = vm .run("samples.arithmetics.extremestack.longs.ExtremeStackLong") .unwrap(); @@ -133,11 +98,7 @@ fn should_do_extreme_stack_operations_with_longs() { #[test] fn should_do_calculate_fibonacci_iteratively() { - let vm = VM::new( - vec!["tests/test_data/samples/arithmetics/fibonacci/iterative/FibonacciIterative.class"], - "tests/test_data/std", - ) - .unwrap(); + let mut vm = VM::new("std"); let last_frame_value = vm .run("samples.arithmetics.fibonacci.iterative.FibonacciIterative") .unwrap(); @@ -146,11 +107,7 @@ fn should_do_calculate_fibonacci_iteratively() { #[test] fn should_do_calculate_fibonacci_recursively() { - let vm = VM::new( - vec!["tests/test_data/samples/arithmetics/fibonacci/recursive/FibonacciRecursive.class"], - "tests/test_data/std", - ) - .unwrap(); + let mut vm = VM::new("std"); let last_frame_value = vm .run("samples.arithmetics.fibonacci.recursive.FibonacciRecursive") .unwrap(); @@ -159,44 +116,28 @@ fn should_do_calculate_fibonacci_recursively() { #[test] fn should_do_arrays() { - let vm = VM::new( - vec!["tests/test_data/samples/arrays/array/ints/ArrayInt.class"], - "tests/test_data/std", - ) - .unwrap(); + let mut vm = VM::new("std"); let last_frame_value = vm.run("samples.arrays.array.ints.ArrayInt").unwrap(); assert_eq!(740, get_int(last_frame_value)) } #[test] fn should_do_arrays_with_longs() { - let vm = VM::new( - vec!["tests/test_data/samples/arrays/array/longs/ArrayLong.class"], - "tests/test_data/std", - ) - .unwrap(); + let mut vm = VM::new("std"); let last_frame_value = vm.run("samples.arrays.array.longs.ArrayLong").unwrap(); assert_eq!(233646220932000, get_long(last_frame_value)) } #[test] fn should_do_3d_arrays() { - let vm = VM::new( - vec!["tests/test_data/samples/arrays/array3d/Array3D.class"], - "tests/test_data/std", - ) - .unwrap(); + let mut vm = VM::new("std"); let last_frame_value = vm.run("samples.arrays.array3d.Array3D").unwrap(); assert_eq!(780, get_int(last_frame_value)) } #[test] fn should_do_class_static_initialization() { - let vm = VM::new( - vec!["tests/test_data/samples/fields/staticinitialization/array/StaticInitializationArray.class"], - "tests/test_data/std", - ) - .unwrap(); + let mut vm = VM::new("std"); let last_frame_value = vm .run("samples.fields.staticinitialization.array.StaticInitializationArray") .unwrap(); @@ -205,15 +146,7 @@ fn should_do_class_static_initialization() { #[test] fn should_do_class_static_initialization_multiple_classes() { - let vm = VM::new( - vec![ - "tests/test_data/samples/fields/staticinitialization/chain/Dependable.class", - "tests/test_data/samples/fields/staticinitialization/chain/DependsOnDependable.class", - "tests/test_data/samples/fields/staticinitialization/chain/StaticInitializationChain.class", - ], - "tests/test_data/std", - ) - .unwrap(); + let mut vm = VM::new("std"); let last_frame_value = vm .run("samples.fields.staticinitialization.chain.StaticInitializationChain") .unwrap(); @@ -222,11 +155,7 @@ fn should_do_class_static_initialization_multiple_classes() { #[test] fn should_do_class_static_initialization_within_one_class() { - let vm = VM::new( - vec!["tests/test_data/samples/fields/staticinitialization/oneclass/StaticInitializationWithinOneClass.class"], - "tests/test_data/std", - ) - .unwrap(); + let mut vm = VM::new("std"); let last_frame_value = vm .run("samples.fields.staticinitialization.oneclass.StaticInitializationWithinOneClass") .unwrap(); @@ -235,17 +164,7 @@ fn should_do_class_static_initialization_within_one_class() { #[test] fn should_do_class_static_initialization_advanced() { - let vm = VM::new( - vec![ - "tests/test_data/samples/fields/staticinitialization/advanced/StaticInitializationAdvanced.class", - "tests/test_data/samples/fields/staticinitialization/advanced/ClassC.class", - "tests/test_data/samples/fields/staticinitialization/advanced/ClassD.class", - "tests/test_data/samples/fields/staticinitialization/advanced/ClassE.class", - "tests/test_data/samples/fields/staticinitialization/advanced/Helper.class", - ], - "tests/test_data/std", - ) - .unwrap(); + let mut vm = VM::new("std"); let last_frame_value = vm .run("samples.fields.staticinitialization.advanced.StaticInitializationAdvanced") .unwrap(); @@ -254,15 +173,7 @@ fn should_do_class_static_initialization_advanced() { #[test] fn should_do_class_static_initialization_circular() { - let vm = VM::new( - vec![ - "tests/test_data/samples/fields/staticinitialization/circular/StaticInitializationCircular.class", - "tests/test_data/samples/fields/staticinitialization/circular/ClassACircular.class", - "tests/test_data/samples/fields/staticinitialization/circular/ClassBCircular.class", - ], - "tests/test_data/std", - ) - .unwrap(); + let mut vm = VM::new("std"); let last_frame_value = vm .run("samples.fields.staticinitialization.circular.StaticInitializationCircular") .unwrap(); diff --git a/tests/test_data/std/Object.class b/tests/test_data/std/java/lang/Object.class similarity index 100% rename from tests/test_data/std/Object.class rename to tests/test_data/std/java/lang/Object.class diff --git a/tests/test_data/std/Object.java b/tests/test_data/std/java/lang/Object.java similarity index 100% rename from tests/test_data/std/Object.java rename to tests/test_data/std/java/lang/Object.java diff --git a/vm/src/class_loader.rs b/vm/src/class_loader.rs deleted file mode 100644 index a22e83a6..00000000 --- a/vm/src/class_loader.rs +++ /dev/null @@ -1,225 +0,0 @@ -use crate::error::{Error, ErrorKind, Result}; -use crate::method_area::field::Field; -use crate::method_area::java_class::{Fields, JavaClass, Methods}; -use crate::method_area::java_method::JavaMethod; -use crate::method_area::method_area::MethodArea; -use crate::util::{get_class_name_by_cpool_class_index, get_cpool_string}; -use jclass::attributes::Attribute; -use jclass::attributes::Attribute::Code; -use jclass::class_file::{parse, ClassFile}; -use jclass::fields::FieldFlags; -use jdescriptor::TypeDescriptor; -use std::cell::RefCell; -use std::collections::HashMap; -use std::fs::File; -use std::io::ErrorKind::Other; -use std::io::Read; -use std::path::PathBuf; -use std::{fs, io}; - -#[derive(Debug)] -pub struct ClassLoader { - method_area: MethodArea, -} - -impl ClassLoader { - pub(crate) fn new(class_file_names: Vec<&str>, std_dir: &str) -> Result { - let mut loaded_classes = HashMap::new(); - - let std_class_names = Self::get_class_files_in_dir(std_dir)?; - - for path in std_class_names { - let (class_name, java_class) = Self::load_class(path.to_str().ok_or( - Error::new_io(io::Error::new(Other, "error getting path".to_string())), - )?)?; - - loaded_classes.insert(class_name, java_class); - } - - for class_file_name in class_file_names { - let (class_name, java_class) = Self::load_class(class_file_name)?; - loaded_classes.insert(class_name.clone(), java_class); - } - - Ok(Self { - method_area: MethodArea::new(loaded_classes), - }) - } - - pub fn method_area(&self) -> &MethodArea { - &self.method_area - } - - fn load_class(class_file_name: &str) -> Result<(String, JavaClass)> { - let mut file = File::open(class_file_name)?; - - let mut buff = Vec::new(); - file.read_to_end(&mut buff)?; - - let class_file = parse(buff.as_slice()) - .map_err(|err| Error::new(ErrorKind::ClassFile(err.to_string())))?; - - Self::to_java_class(class_file) - } - - fn to_java_class(class_file: ClassFile) -> Result<(String, JavaClass)> { - let class_name = Self::get_class_name(&class_file)?; - let methods = Self::get_methods(&class_file, class_name.as_str())?; - let static_fields = Self::get_static_fields(&class_file)?; - let non_static_fields_descriptors = Self::get_non_static_fields_descriptors(&class_file)?; - - Ok(( - class_name.clone(), - JavaClass::new( - methods, - static_fields, - non_static_fields_descriptors, - class_file, - ), - )) - } - - fn get_methods(class_file: &ClassFile, class_name: &str) -> Result { - let methods = class_file.methods(); - let mut method_by_signature: HashMap = HashMap::new(); - - for method in methods.iter() { - let method_name = get_cpool_string(class_file, method.name_index() as usize) - .ok_or(Error::new_constant_pool("Error getting method name"))?; - let method_signature = - get_cpool_string(class_file, method.descriptor_index() as usize).ok_or( - Error::new_constant_pool("Error getting method method_signature"), - )?; - let (max_stack, max_locals, code) = Self::get_cpool_code_attribute( - method.attributes(), - ) - .ok_or(Error::new_constant_pool( - format!("Error getting method code: {class_name}.{method_name}(...)").as_str(), - ))?; - let key_signature = method_name + ":" + method_signature.as_str(); - - method_by_signature.insert( - key_signature.clone(), - JavaMethod::new( - method_signature - .as_str() - .parse() - .map_err(|err| Error::new(ErrorKind::ClassFile(err)))?, - max_stack, - max_locals, - code, - class_name, - ), - ); - } - - Ok(Methods::new(method_by_signature)) - } - - fn get_static_fields(class_file: &ClassFile) -> Result { - let field_by_name = class_file - .fields() - .iter() - .filter_map(|field| { - if field.access_flags().contains(FieldFlags::ACC_STATIC) { - let field_name = get_cpool_string(class_file, field.name_index() as usize) - .ok_or_else(|| Error::new_constant_pool("Error getting field name")) - .ok()?; - - let field_signature = - get_cpool_string(class_file, field.descriptor_index() as usize) - .ok_or_else(|| { - Error::new_constant_pool("Error getting field signature") - }) - .ok()?; - - let result = str::parse::(field_signature.as_str()) - .map_err(|e| Error::new_constant_pool(e.as_str())) //todo: return proper error type? - .ok()?; - - Some((field_name, RefCell::new(Field::new(result)))) - } else { - None - } - }) - .collect(); - - Ok(Fields::new(field_by_name)) - } - - // todo: this helper and `JavaClass.non_static_fields_descriptors` field are for PUTFIELD ops - refactor this approach - fn get_non_static_fields_descriptors( - class_file: &ClassFile, - ) -> Result> { - let non_static_fields_descriptors = class_file - .fields() - .iter() - .filter_map(|field| { - if !field.access_flags().contains(FieldFlags::ACC_STATIC) { - let field_name = get_cpool_string(class_file, field.name_index() as usize) - .ok_or_else(|| Error::new_constant_pool("Error getting field name")) - .ok()?; - - let field_signature = - get_cpool_string(class_file, field.descriptor_index() as usize) - .ok_or_else(|| { - Error::new_constant_pool("Error getting field signature") - }) - .ok()?; - - let result = str::parse::(field_signature.as_str()) - .map_err(|e| Error::new_constant_pool(e.as_str())) //todo: return proper error type? - .ok()?; - - Some((field_name, result)) - } else { - None - } - }) - .collect(); - - Ok(non_static_fields_descriptors) - } - - fn get_cpool_code_attribute(attributes: &Vec) -> Option<(u16, u16, Vec)> { - attributes.iter().find_map(|item| { - if let Code { - max_stack, - max_locals, - code, - .. - } = item - { - Some((*max_stack, *max_locals, code.clone())) - } else { - None - } - }) - } - - fn get_class_name(class_file: &ClassFile) -> Result { - let this_class_index = class_file.this_class() as usize; - - get_class_name_by_cpool_class_index(this_class_index, class_file) - .ok_or(Error::new_constant_pool("error getting class name")) - } - - fn get_class_files_in_dir(std_dir: &str) -> Result> { - let mut class_files = Vec::new(); - let entries = fs::read_dir(std_dir)?; - - for entry in entries { - let entry = entry?; - let path = entry.path(); - if path.is_file() { - if let Some(extension) = path.extension() { - if extension == "class" { - class_files.push(path); - } - } - } - } - - Ok(class_files) - } -} diff --git a/vm/src/error.rs b/vm/src/error.rs index 16912958..c0d183c0 100644 --- a/vm/src/error.rs +++ b/vm/src/error.rs @@ -20,10 +20,6 @@ impl Error { Self::new(Execution(String::from(descr))) } - pub(crate) fn new_io(err: io::Error) -> Self { - Self::new(Io(err)) - } - pub fn kind(&self) -> &ErrorKind { &self.0 } diff --git a/vm/src/execution_engine/engine.rs b/vm/src/execution_engine/engine.rs index 27da625a..eea9c1a2 100644 --- a/vm/src/execution_engine/engine.rs +++ b/vm/src/execution_engine/engine.rs @@ -5,18 +5,17 @@ use crate::heap::java_instance::JavaInstance; use crate::method_area::java_method::JavaMethod; use crate::method_area::method_area::MethodArea; use crate::stack::stack_frame::{i32toi64, StackFrame}; -use crate::util::{ - get_class_name_by_cpool_class_index, get_cpool_integer, get_cpool_long_double, Primitive, -}; use jdescriptor::get_length; use std::collections::HashSet; pub(crate) struct Engine<'a> { method_area: &'a MethodArea, - heap: Heap<'a>, + heap: Heap, } impl<'a> Engine<'a> { + const STATIC_INIT_METHOD: &'static str = ":()V"; + pub(crate) fn execute( &mut self, method: &JavaMethod, @@ -89,16 +88,17 @@ impl<'a> Engine<'a> { } LDC => { stack_frame.incr_pc(); - let cpoolindex = stack_frame.get_bytecode_byte() as usize; + let cpoolindex = stack_frame.get_bytecode_byte() as u16; - let java_class = self - .method_area - .loaded_classes - .get(current_class_name.as_str()) - .unwrap(); + let java_class = self.method_area.get(current_class_name.as_str())?; + let cpool_helper = java_class.cpool_helper(); // todo add support of other types - let value = get_cpool_integer(&java_class.class_file, cpoolindex).unwrap(); + let value = cpool_helper.get_integer(cpoolindex).ok_or_else(|| { + Error::new_constant_pool(&format!( + "Error getting value as Integer by index {cpoolindex}" + )) + })?; stack_frame.push(value); @@ -106,22 +106,18 @@ impl<'a> Engine<'a> { println!("LDC -> cpoolindex={cpoolindex}, value={value}"); } LDC2_W => { - let cpoolindex = Self::extract_two_bytes(stack_frame) as usize; - - let java_class = self - .method_area - .loaded_classes - .get(current_class_name.as_str()) - .unwrap(); - - let value = match get_cpool_long_double(&java_class.class_file, cpoolindex) { - Some(Primitive::Double(_v)) => todo!("add support for double"), - Some(Primitive::Long(v)) => { - stack_frame.push_i64(v); - v - } - _ => unreachable!(), - }; + let cpoolindex = Self::extract_two_bytes(stack_frame); + + let java_class = self.method_area.get(current_class_name.as_str())?; + let cpool_helper = java_class.cpool_helper(); + + // todo add support of other types + let value = cpool_helper.get_long(cpoolindex).ok_or_else(|| { + Error::new_constant_pool(&format!( + "Error getting value as Long by index {cpoolindex}" + )) + })?; + stack_frame.push_i64(value); stack_frame.incr_pc(); println!("LDC2_W -> cpoolindex={cpoolindex}, value={value}"); @@ -689,42 +685,38 @@ impl<'a> Engine<'a> { GETSTATIC => { let fieldref_constpool_index = Self::extract_two_bytes(stack_frame); - let java_class = self - .method_area - .loaded_classes - .get(current_class_name.as_str()) - .unwrap(); + let java_class = self.method_area.get(current_class_name.as_str())?; + let cpool_helper = java_class.cpool_helper(); - let (class_name, field_name) = - self.method_area.get_fieldname_by_fieldref_cpool_index( - java_class, - fieldref_constpool_index, - )?; + let (class_name, field_name, _) = + cpool_helper.get_full_field_info(fieldref_constpool_index) + .ok_or_else(|| Error::new_constant_pool(&format!("Error getting full field info by index {fieldref_constpool_index}")))?; //calling static block if needed, todo: move me to single place if !static_set.contains(class_name.as_str()) { static_set.insert(class_name.clone()); - if let Ok(clinit) = self - .method_area - .get_method_by_name_signature(class_name.as_str(), ":()V") + let rc = self.method_area.get(class_name.as_str())?; + if let Some(clinit) = + rc.methods.method_by_signature.get(Self::STATIC_INIT_METHOD) { stack_frame.advance_pc(-2); let next_frame = clinit.new_stack_frame(); stack_frames.push(next_frame); - println!(" -> {class_name}.:()V"); + println!(" -> {class_name}.{}", Self::STATIC_INIT_METHOD); continue; } } - let field = self - .method_area - .loaded_classes - .get(&class_name) - .unwrap() + let rc = self.method_area.get(&class_name)?; + let field = rc .static_fields .field_by_name .get(&field_name) - .unwrap() + .ok_or_else(|| { + Error::new_constant_pool(&format!( + "Error getting field: {class_name}.{field_name}" + )) + })? .borrow(); field @@ -742,43 +734,39 @@ impl<'a> Engine<'a> { PUTSTATIC => { let fieldref_constpool_index = Self::extract_two_bytes(stack_frame); - let java_class = self - .method_area - .loaded_classes - .get(current_class_name.as_str()) - .unwrap(); + let rc = self.method_area.get(current_class_name.as_str())?; - let (class_name, field_name) = - self.method_area.get_fieldname_by_fieldref_cpool_index( - java_class, - fieldref_constpool_index, - )?; + let cpool_helper = rc.cpool_helper(); + let (class_name, field_name, _) = + cpool_helper.get_full_field_info(fieldref_constpool_index) + .ok_or_else(|| Error::new_constant_pool(&format!("Error getting full field info by index {fieldref_constpool_index}")))?; //calling static block if needed, todo: move me to single place if !static_set.contains(class_name.as_str()) { static_set.insert(class_name.clone()); - if let Ok(clinit) = self - .method_area - .get_method_by_name_signature(class_name.as_str(), ":()V") + let rc = self.method_area.get(class_name.as_str())?; + if let Some(clinit) = + rc.methods.method_by_signature.get(Self::STATIC_INIT_METHOD) { stack_frame.advance_pc(-2); let next_frame = clinit.new_stack_frame(); stack_frames.push(next_frame); - println!(" -> {class_name}.:()V"); + println!(" -> {class_name}.{}", Self::STATIC_INIT_METHOD); continue; } } let len = { - let field = self - .method_area - .loaded_classes - .get(&class_name) - .unwrap() + let rc = self.method_area.get(&class_name)?; + let field = rc .static_fields .field_by_name .get(&field_name) - .unwrap() + .ok_or_else(|| { + Error::new_constant_pool(&format!( + "Error getting field: {class_name}.{field_name}" + )) + })? .borrow(); get_length(field.type_descriptor()) @@ -801,18 +789,13 @@ impl<'a> Engine<'a> { GETFIELD => { let fieldref_constpool_index = Self::extract_two_bytes(stack_frame); - let java_class = self - .method_area - .loaded_classes - .get(current_class_name.as_str()) - .unwrap(); + let rc = self.method_area.get(current_class_name.as_str())?; + let cpool_helper = rc.cpool_helper(); let objectref = stack_frame.pop(); - let (class_name, field_name) = - self.method_area.get_fieldname_by_fieldref_cpool_index( - java_class, - fieldref_constpool_index, - )?; + let (class_name, field_name, _) = + cpool_helper.get_full_field_info(fieldref_constpool_index) + .ok_or_else(|| Error::new_constant_pool(&format!("Error getting full field info by index {fieldref_constpool_index}")))?; let value = self .heap @@ -826,26 +809,23 @@ impl<'a> Engine<'a> { PUTFIELD => { let fieldref_constpool_index = Self::extract_two_bytes(stack_frame); - let java_class = self - .method_area - .loaded_classes - .get(current_class_name.as_str()) - .unwrap(); - - let (class_name, field_name) = - self.method_area.get_fieldname_by_fieldref_cpool_index( - java_class, - fieldref_constpool_index, - )?; - - let type_descriptor = self - .method_area - .loaded_classes - .get(&class_name) - .unwrap() - .non_static_fields_descriptors + let rc = self.method_area.get(current_class_name.as_str())?; + let cpool_helper = rc.cpool_helper(); + + let (class_name, field_name, _) = + cpool_helper.get_full_field_info(fieldref_constpool_index) + .ok_or_else(|| Error::new_constant_pool(&format!("Error getting full field info by index {fieldref_constpool_index}")))?; + + let rc = self.method_area.get(&class_name)?; + let type_descriptor = rc + .field_descriptors + .descriptor_by_name .get(&field_name) - .unwrap(); + .ok_or_else(|| { + Error::new_constant_pool(&format!( + "Error getting type descriptor for {class_name}.{field_name}" + )) + })?; let len = get_length(type_descriptor); let mut value = Vec::with_capacity(len); @@ -867,17 +847,20 @@ impl<'a> Engine<'a> { INVOKEVIRTUAL => { let methodref_constpool_index = Self::extract_two_bytes(stack_frame); - let java_class = self - .method_area - .loaded_classes - .get(current_class_name.as_str()) - .unwrap(); - - let (class_name, method_name, virtual_method) = - self.method_area.get_method_by_methodref_cpool_index( - java_class, - methodref_constpool_index, - )?; + let rc = self.method_area.get(current_class_name.as_str())?; + let cpool_helper = rc.cpool_helper(); + + let (class_name, method_name, method_descriptor) = cpool_helper + .get_full_method_info(methodref_constpool_index) + .ok_or_else(|| Error::new_constant_pool(&format!("Error getting full method info by index {methodref_constpool_index}")))?; + let full_signature = format!("{}:{}", method_name, method_descriptor); + let rc = self.method_area.get(class_name.as_str())?; + let virtual_method = rc + .methods + .method_by_signature + .get(&full_signature) + .ok_or_else(|| Error::new_constant_pool(&format!("Error getting JavaMethod by class name {class_name} and full signature {full_signature}")))?; + // ^^^ todo: implement lookup by instance type let mut next_frame = virtual_method.new_stack_frame(); let arg_num = virtual_method.get_signature().arguments_length(); @@ -898,17 +881,20 @@ impl<'a> Engine<'a> { INVOKESPECIAL => { let methodref_constpool_index = Self::extract_two_bytes(stack_frame); - let java_class = self - .method_area - .loaded_classes - .get(current_class_name.as_str()) - .unwrap(); - - let (class_name, method_name, special_method) = - self.method_area.get_method_by_methodref_cpool_index( - java_class, - methodref_constpool_index, - )?; + let rc = self.method_area.get(current_class_name.as_str())?; + let cpool_helper = rc.cpool_helper(); + + let (class_name, method_name, method_descriptor) = cpool_helper + .get_full_method_info(methodref_constpool_index) + .ok_or_else(|| Error::new_constant_pool(&format!("Error getting full method info by index {methodref_constpool_index}")))?; + let full_signature = format!("{}:{}", method_name, method_descriptor); + let rc = self.method_area.get(class_name.as_str())?; + let special_method = rc + .methods + .method_by_signature + .get(&full_signature) + .ok_or_else(|| Error::new_constant_pool(&format!("Error getting JavaMethod by class name {class_name} and full signature {full_signature}")))?; + // ^^^ todo: implement lookup in parents let mut next_frame = special_method.new_stack_frame(); let arg_num = special_method.get_signature().arguments_length(); @@ -927,30 +913,32 @@ impl<'a> Engine<'a> { } INVOKESTATIC => { let methodref_constpool_index = Self::extract_two_bytes(stack_frame); - let java_class = self - .method_area - .loaded_classes - .get(current_class_name.as_str()) - .unwrap(); - - let (class_name, method_name, static_method) = - self.method_area.get_method_by_methodref_cpool_index( - java_class, - methodref_constpool_index, - )?; + let rc = self.method_area.get(current_class_name.as_str())?; + let cpool_helper = rc.cpool_helper(); + + let (class_name, method_name, method_descriptor) = cpool_helper + .get_full_method_info(methodref_constpool_index) + .ok_or_else(|| Error::new_constant_pool(&format!("Error getting full method info by index {methodref_constpool_index}")))?; + let full_signature = format!("{}:{}", method_name, method_descriptor); + let rc = self.method_area.get(class_name.as_str())?; + let static_method = rc + .methods + .method_by_signature + .get(&full_signature) + .ok_or_else(|| Error::new_constant_pool(&format!("Error getting JavaMethod by class name {class_name} and full signature {full_signature}")))?; //calling static block if needed, todo: move me to single place // requirements of JVMS Section 5.4 if !static_set.contains(class_name.as_str()) { static_set.insert(class_name.clone()); - if let Ok(clinit) = self - .method_area - .get_method_by_name_signature(class_name.as_str(), ":()V") + let rc = self.method_area.get(class_name.as_str())?; + if let Some(clinit) = + rc.methods.method_by_signature.get(Self::STATIC_INIT_METHOD) { stack_frame.advance_pc(-2); let next_frame = clinit.new_stack_frame(); stack_frames.push(next_frame); - println!(" -> {class_name}.:()V"); + println!(" -> {class_name}.{}", Self::STATIC_INIT_METHOD); continue; } } @@ -968,29 +956,20 @@ impl<'a> Engine<'a> { println!("INVOKESTATIC -> {class_name}.{method_name}(...)"); } NEW => { - let class_constpool_index = Self::extract_two_bytes(stack_frame) as usize; - - let java_class = self - .method_area - .loaded_classes - .get(current_class_name.as_str()) - .unwrap(); - let class_to_invoke_new_for = get_class_name_by_cpool_class_index( - class_constpool_index, - &java_class.class_file, - ) - .unwrap(); - let default_field_values_instance = JavaInstance::new( - self.method_area - .loaded_classes - .get(class_to_invoke_new_for.as_str()) - .expect( - format!( - "class_to_invoke_new_for not found: {class_to_invoke_new_for}" - ) - .as_str(), - ), - )?; + let class_constpool_index = Self::extract_two_bytes(stack_frame); + + let rc = self.method_area.get(current_class_name.as_str())?; + let cpool_helper = rc.cpool_helper(); + + let class_to_invoke_new_for = cpool_helper + .get_class_name(class_constpool_index) + .ok_or_else(|| { + Error::new_constant_pool(&format!( + "Error getting class name by index {class_constpool_index}" + )) + })?; + let rc = self.method_area.get(class_to_invoke_new_for.as_str())?; + let default_field_values_instance = JavaInstance::new(rc)?; let instanceref = self.heap.create_instance(default_field_values_instance); stack_frame.push(instanceref); @@ -1013,18 +992,17 @@ impl<'a> Engine<'a> { ANEWARRAY => { let length = stack_frame.pop(); - let class_constpool_index = Self::extract_two_bytes(stack_frame) as usize; - let java_class = self - .method_area - .loaded_classes - .get(current_class_name.as_str()) - .unwrap(); - let class_of_array = get_class_name_by_cpool_class_index( - class_constpool_index, - &java_class.class_file, - ) - .unwrap(); - + let class_constpool_index = Self::extract_two_bytes(stack_frame); + let rc = self.method_area.get(current_class_name.as_str())?; + let cpool_helper = rc.cpool_helper(); + + let class_of_array = cpool_helper + .get_class_name(class_constpool_index) + .ok_or_else(|| { + Error::new_constant_pool(&format!( + "Error getting class name by index {class_constpool_index}" + )) + })?; let arrayref = self.heap.create_array(length); stack_frame.push(arrayref); diff --git a/vm/src/heap/heap.rs b/vm/src/heap/heap.rs index d611bcab..0f17cd7b 100644 --- a/vm/src/heap/heap.rs +++ b/vm/src/heap/heap.rs @@ -4,12 +4,12 @@ use crate::heap::java_instance::{Array, HeapValue, JavaInstance}; use std::collections::HashMap; #[derive(Debug)] -pub(crate) struct Heap<'a> { - data: HashMap>, +pub(crate) struct Heap { + data: HashMap, next_id: i32, } -impl<'a> Heap<'a> { +impl Heap { pub fn new() -> Self { Self { data: HashMap::new(), @@ -17,7 +17,7 @@ impl<'a> Heap<'a> { } } - pub fn create_instance(&mut self, java_instance: JavaInstance<'a>) -> i32 { + pub fn create_instance(&mut self, java_instance: JavaInstance) -> i32 { self.next_id = self.next_id + 1; //todo: make me atomic self.data.insert(self.next_id, Object(java_instance)); diff --git a/vm/src/heap/java_instance.rs b/vm/src/heap/java_instance.rs index c56e7050..bd3b4bc4 100644 --- a/vm/src/heap/java_instance.rs +++ b/vm/src/heap/java_instance.rs @@ -1,13 +1,12 @@ use crate::error::Error; use crate::method_area::field::Field; use crate::method_area::java_class::JavaClass; -use crate::util::get_fields; use std::collections::HashMap; +use std::rc::Rc; #[derive(Debug)] -pub(crate) struct JavaInstance<'a> { - #[allow(dead_code)] - class_ref: &'a JavaClass, +pub(crate) struct JavaInstance { + _class_ref: Rc, // todo use me or delete fields: HashMap, } @@ -46,16 +45,21 @@ impl Array { } #[derive(Debug)] -pub(crate) enum HeapValue<'a> { - Object(JavaInstance<'a>), +pub(crate) enum HeapValue { + Object(JavaInstance), Arr(Array), } -impl<'a> JavaInstance<'a> { - pub fn new(class_ref: &'a JavaClass) -> crate::error::Result { +impl<'a> JavaInstance { + pub fn new(class_ref: Rc) -> crate::error::Result { Ok(Self { - class_ref, - fields: get_fields(&class_ref.class_file)?, + _class_ref: Rc::clone(&class_ref), + fields: class_ref //todo: refactor me: remove static fields, put this code in right place + .field_descriptors + .descriptor_by_name + .iter() + .map(|(name, descriptor)| (name.clone(), Field::new(descriptor.clone()))) + .collect(), }) } @@ -67,7 +71,11 @@ impl<'a> JavaInstance<'a> { self.fields .get_mut(fieldname) .and_then(|v| Some(v.set_raw_value(value))) - .ok_or(Error::new_execution("error setting instance field value")) + .ok_or_else(|| { + Error::new_execution(&format!( + "error setting value for instance field {fieldname}" + )) + }) } pub fn get_field_value(&self, fieldname: &str) -> crate::error::Result<&Vec> { diff --git a/vm/src/lib.rs b/vm/src/lib.rs index 1c31a768..010473fb 100644 --- a/vm/src/lib.rs +++ b/vm/src/lib.rs @@ -2,8 +2,6 @@ mod error; mod stack; pub mod vm; -mod class_loader; mod execution_engine; mod heap; mod method_area; -mod util; diff --git a/vm/src/method_area/attributes_helper.rs b/vm/src/method_area/attributes_helper.rs new file mode 100644 index 00000000..d6875488 --- /dev/null +++ b/vm/src/method_area/attributes_helper.rs @@ -0,0 +1,184 @@ +use jclass::attributes::Attribute; +use std::collections::HashMap; + +pub struct AttributesHelper { + data: HashMap, +} + +#[derive(Eq, Hash, PartialEq, Debug)] +pub enum AttributeType { + ConstantValue, + Code, + Exceptions, + SourceFile, + LineNumberTable, + LocalVariableTable, + InnerClasses, + Synthetic, + Deprecated, + EnclosingMethod, + Signature, + SourceDebugExtension, + LocalVariableTypeTable, + RuntimeVisibleAnnotations, + RuntimeInvisibleAnnotations, + RuntimeVisibleParameterAnnotations, + RuntimeInvisibleParameterAnnotations, + AnnotationDefault, + StackMapTable, + BootstrapMethods, + RuntimeVisibleTypeAnnotations, + RuntimeInvisibleTypeAnnotations, + MethodParameters, + Module, + ModulePackages, + ModuleMainClass, + NestHost, + NestMembers, + Record, + PermittedSubclasses, +} + +impl From<&Attribute> for AttributeType { + fn from(value: &Attribute) -> Self { + match value { + Attribute::ConstantValue { .. } => AttributeType::ConstantValue, + Attribute::Code { .. } => AttributeType::Code, + Attribute::Exceptions { .. } => AttributeType::Exceptions, + Attribute::SourceFile { .. } => AttributeType::SourceFile, + Attribute::LineNumberTable { .. } => AttributeType::LineNumberTable, + Attribute::LocalVariableTable { .. } => AttributeType::LocalVariableTable, + Attribute::InnerClasses { .. } => AttributeType::InnerClasses, + Attribute::Synthetic => AttributeType::Synthetic, + Attribute::Deprecated => AttributeType::Deprecated, + Attribute::EnclosingMethod { .. } => AttributeType::EnclosingMethod, + Attribute::Signature { .. } => AttributeType::Signature, + Attribute::SourceDebugExtension => AttributeType::SourceDebugExtension, + Attribute::LocalVariableTypeTable { .. } => AttributeType::LocalVariableTypeTable, + Attribute::RuntimeVisibleAnnotations { .. } => { + AttributeType::RuntimeVisibleAnnotations + } + Attribute::RuntimeInvisibleAnnotations { .. } => { + AttributeType::RuntimeInvisibleAnnotations + } + Attribute::RuntimeVisibleParameterAnnotations => { + AttributeType::RuntimeVisibleParameterAnnotations + } + Attribute::RuntimeInvisibleParameterAnnotations => { + AttributeType::RuntimeInvisibleParameterAnnotations + } + Attribute::AnnotationDefault { .. } => AttributeType::AnnotationDefault, + Attribute::StackMapTable { .. } => AttributeType::StackMapTable, + Attribute::BootstrapMethods { .. } => AttributeType::BootstrapMethods, + Attribute::RuntimeVisibleTypeAnnotations => { + AttributeType::RuntimeVisibleTypeAnnotations + } + Attribute::RuntimeInvisibleTypeAnnotations => { + AttributeType::RuntimeInvisibleTypeAnnotations + } + Attribute::MethodParameters { .. } => AttributeType::MethodParameters, + Attribute::Module => AttributeType::Module, + Attribute::ModulePackages => AttributeType::ModulePackages, + Attribute::ModuleMainClass => AttributeType::ModuleMainClass, + Attribute::NestHost { .. } => AttributeType::NestHost, + Attribute::NestMembers { .. } => AttributeType::NestMembers, + Attribute::Record { .. } => AttributeType::Record, + Attribute::PermittedSubclasses { .. } => AttributeType::PermittedSubclasses, + } + } +} + +impl AttributesHelper { + pub fn new(attributes: &[Attribute]) -> Self { + Self { + data: attributes + .iter() + .map(|attribute| (attribute.into(), attribute.clone())) + .collect(), + } + } + + pub fn get_code(&self) -> Option<(u16, u16, Vec)> { + match self.data.get(&AttributeType::Code)? { + Attribute::Code { + max_stack, + max_locals, + code, + .. + } => Some((*max_stack, *max_locals, code.clone())), + _ => None, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use jclass::attributes::Attribute::{ + Code, LineNumberTable, LocalVariableTable, MethodParameters, + }; + use jclass::attributes::{ + LineNumberRecord, LocalVariableTableRecord, MethodParameterFlags, MethodParameterRecord, + }; + use std::collections::HashMap; + + #[test] + fn should_create_attribute_map() { + let code = Code { + max_stack: 2, + max_locals: 2, + code: vec![0x2a, 0xb7, 0x0, 0x1, 0x2a, 0x1b, 0xb5, 0x0, 0x7, 0xb1], + exception_table: vec![], + attributes: vec![ + LineNumberTable { + line_number_table: vec![LineNumberRecord::new(0, 4)], + }, + LocalVariableTable { + local_variable_table: vec![ + LocalVariableTableRecord::new(0, 10, 29, 30, 0), + LocalVariableTableRecord::new(0, 10, 11, 12, 1), + ], + }, + ], + }; + let method_parameters = MethodParameters { + parameters: vec![MethodParameterRecord::new( + 11, + MethodParameterFlags::empty(), + )], + }; + let attributes = vec![code.clone(), method_parameters.clone()]; + let actual = AttributesHelper::new(&attributes); + + let mut expected = HashMap::new(); + expected.insert(AttributeType::Code, code); + expected.insert(AttributeType::MethodParameters, method_parameters); + + assert_eq!(expected, actual.data); + } + + #[test] + fn should_return_code_attribute() { + let code = Code { + max_stack: 2, + max_locals: 4, + code: vec![0x2a, 0xb7, 0x0, 0x1], + exception_table: vec![], + attributes: vec![], + }; + let method_parameters = MethodParameters { + parameters: vec![MethodParameterRecord::new( + 11, + MethodParameterFlags::empty(), + )], + }; + let attributes = vec![code.clone(), method_parameters.clone()]; + + let attributes_helper = AttributesHelper::new(&attributes); + + assert_eq!( + Some((2, 4, vec![0x2a, 0xb7, 0x0, 0x1])), + attributes_helper.get_code() + ); + } +} diff --git a/vm/src/method_area/cpool_helper.rs b/vm/src/method_area/cpool_helper.rs new file mode 100644 index 00000000..862cb53a --- /dev/null +++ b/vm/src/method_area/cpool_helper.rs @@ -0,0 +1,468 @@ +use jclass::constant_pool::ConstantPool; +use std::collections::HashMap; + +#[derive(Debug)] +pub struct CPoolHelper { + data: HashMap>, +} + +#[derive(Eq, Hash, PartialEq, Debug)] +pub enum CPoolType { + Empty, + Utf8, + Integer, + Float, + Long, + Double, + Class, + String, + Fieldref, + Methodref, + InterfaceMethodref, + NameAndType, + MethodHandle, + MethodType, + Dynamic, + InvokeDynamic, + Module, + Package, +} + +impl From<&ConstantPool> for CPoolType { + fn from(item: &ConstantPool) -> Self { + match item { + ConstantPool::Empty => CPoolType::Empty, + ConstantPool::Utf8 { .. } => CPoolType::Utf8, + ConstantPool::Integer { .. } => CPoolType::Integer, + ConstantPool::Float { .. } => CPoolType::Float, + ConstantPool::Long { .. } => CPoolType::Long, + ConstantPool::Double { .. } => CPoolType::Double, + ConstantPool::Class { .. } => CPoolType::Class, + ConstantPool::String { .. } => CPoolType::String, + ConstantPool::Fieldref { .. } => CPoolType::Fieldref, + ConstantPool::Methodref { .. } => CPoolType::Methodref, + ConstantPool::InterfaceMethodref { .. } => CPoolType::InterfaceMethodref, + ConstantPool::NameAndType { .. } => CPoolType::NameAndType, + ConstantPool::MethodHandle { .. } => CPoolType::MethodHandle, + ConstantPool::MethodType { .. } => CPoolType::MethodType, + ConstantPool::Dynamic { .. } => CPoolType::Dynamic, + ConstantPool::InvokeDynamic { .. } => CPoolType::InvokeDynamic, + ConstantPool::Module { .. } => CPoolType::Module, + ConstantPool::Package { .. } => CPoolType::Package, + } + } +} + +impl CPoolHelper { + pub fn new(cpool: &[ConstantPool]) -> Self { + let mut data: HashMap> = HashMap::new(); + + for (index, item) in cpool.iter().enumerate() { + let ctype = item.into(); + let entry = data.entry(ctype).or_insert_with(HashMap::new); + entry.insert(index as u16, item.clone()); + } + + Self { data } + } + + pub fn get(&self, ctype: CPoolType, index: u16) -> Option<&ConstantPool> { + self.data.get(&ctype)?.get(&index) + } + + pub fn get_class_name(&self, index: u16) -> Option { + let name_index = match self.get(CPoolType::Class, index)? { + ConstantPool::Class { name_index } => name_index, + _ => return None, + }; + + self.get_utf8(*name_index) + } + + pub fn get_integer(&self, index: u16) -> Option { + match self.get(CPoolType::Integer, index)? { + ConstantPool::Integer { value } => Some(*value), + _ => None, + } + } + + pub fn get_long(&self, index: u16) -> Option { + match self.get(CPoolType::Long, index)? { + ConstantPool::Long { value } => Some(*value), + _ => None, + } + } + + pub fn get_utf8(&self, index: u16) -> Option { + match self.get(CPoolType::Utf8, index)? { + ConstantPool::Utf8 { value } => Some(value.clone()), + _ => None, + } + } + + pub fn get_full_field_info(&self, index: u16) -> Option<(String, String, String)> { + let (class_index, name_and_type_index) = match self.get(CPoolType::Fieldref, index)? { + ConstantPool::Fieldref { + class_index, + name_and_type_index, + } => Some((class_index, name_and_type_index)), + _ => None, + }?; + + let class_name = self.get_class_name(*class_index)?; + let (field_name, field_descriptor) = self.get_name_and_type(*name_and_type_index)?; + + Some((class_name, field_name, field_descriptor)) + } + + pub fn get_full_method_info(&self, index: u16) -> Option<(String, String, String)> { + let (class_index, name_and_type_index) = match self.get(CPoolType::Methodref, index)? { + ConstantPool::Methodref { + class_index, + name_and_type_index, + } => Some((class_index, name_and_type_index)), + _ => None, + }?; + + let class_name = self.get_class_name(*class_index)?; + let (method_name, method_descriptor) = self.get_name_and_type(*name_and_type_index)?; + + Some((class_name, method_name, method_descriptor)) + } + + pub fn get_name_and_type(&self, index: u16) -> Option<(String, String)> { + let (name_index, descriptor_index) = match self.get(CPoolType::NameAndType, index)? { + ConstantPool::NameAndType { + name_index, + descriptor_index, + } => Some((name_index, descriptor_index)), + _ => None, + }?; + + let name = self.get_utf8(*name_index)?; + let descriptor = self.get_utf8(*descriptor_index)?; + + Some((name, descriptor)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use jclass::constant_pool::ConstantPool::{ + Class, Empty, Fieldref, Integer, Long, Methodref, NameAndType, Utf8, + }; + + #[test] + fn should_create_internal_map() { + let cpool = vec![ + Empty, // 0 + Class { + // 1 + name_index: 2, + }, + Utf8 { + // 2 + value: "Trivial$1LocalCls".into(), + }, + Class { + // 3 + name_index: 4, + }, + Utf8 { + // 4 + value: "java/lang/Object".into(), + }, + Utf8 { + // 5 + value: "SourceFile".into(), + }, + Utf8 { + // 6 + value: "Trivial.java".into(), + }, + Utf8 { + // 7 + value: "EnclosingMethod".into(), + }, + Class { + // 8 + name_index: 9, + }, + Utf8 { + // 9 + value: "Trivial".into(), + }, + NameAndType { + // 10 + name_index: 11, + descriptor_index: 12, + }, + Utf8 { + // 11 + value: "run".into(), + }, + Utf8 { + // 12 + value: "()V".into(), + }, + Utf8 { + // 13 + value: "NestHost".into(), + }, + Utf8 { + // 14 + value: "InnerClasses".into(), + }, + Utf8 { + // 15 + value: "LocalCls".into(), + }, + ]; + let actual = CPoolHelper::new(&cpool); + + let mut expected = HashMap::new(); + + let mut empty = HashMap::new(); + empty.insert(0, Empty); + expected.insert(CPoolType::Empty, empty); + + let mut class = HashMap::new(); + class.insert(1, Class { name_index: 2 }); + class.insert(3, Class { name_index: 4 }); + class.insert(8, Class { name_index: 9 }); + expected.insert(CPoolType::Class, class); + + let mut name_name_type = HashMap::new(); + name_name_type.insert( + 10, + NameAndType { + name_index: 11, + descriptor_index: 12, + }, + ); + expected.insert(CPoolType::NameAndType, name_name_type); + + let mut utf8 = HashMap::new(); + utf8.insert( + 2, + Utf8 { + value: "Trivial$1LocalCls".to_string(), + }, + ); + utf8.insert( + 4, + Utf8 { + value: "java/lang/Object".to_string(), + }, + ); + utf8.insert( + 5, + Utf8 { + value: "SourceFile".to_string(), + }, + ); + utf8.insert( + 6, + Utf8 { + value: "Trivial.java".to_string(), + }, + ); + utf8.insert( + 7, + Utf8 { + value: "EnclosingMethod".to_string(), + }, + ); + utf8.insert( + 9, + Utf8 { + value: "Trivial".to_string(), + }, + ); + utf8.insert( + 11, + Utf8 { + value: "run".to_string(), + }, + ); + utf8.insert( + 12, + Utf8 { + value: "()V".to_string(), + }, + ); + utf8.insert( + 13, + Utf8 { + value: "NestHost".to_string(), + }, + ); + utf8.insert( + 14, + Utf8 { + value: "InnerClasses".to_string(), + }, + ); + utf8.insert( + 15, + Utf8 { + value: "LocalCls".to_string(), + }, + ); + expected.insert(CPoolType::Utf8, utf8); + + assert_eq!(expected, actual.data); + } + + #[test] + fn should_return_none_when_type_is_not_present() { + let resolver = CPoolHelper::new(&vec![Empty, Class { name_index: 10 }]); + + let actual = resolver.get(CPoolType::Double, 1); + assert_eq!(None, actual) + } + + #[test] + fn should_return_none_when_index_in_not_matched() { + let resolver = CPoolHelper::new(&vec![Empty, Class { name_index: 10 }]); + + let actual = resolver.get(CPoolType::Class, 2); + assert_eq!(None, actual) + } + + #[test] + fn should_return_value_when_type_and_index_are_present() { + let resolver = CPoolHelper::new(&vec![Empty, Class { name_index: 10 }]); + + let actual = resolver.get(CPoolType::Class, 1); + assert_eq!(Some(Class { name_index: 10 }), actual.cloned()) + } + + #[test] + fn should_return_class_name() { + let resolver = CPoolHelper::new(&vec![ + Empty, + Class { name_index: 2 }, + Utf8 { + value: "java/lang/Byte".to_string(), + }, + ]); + + let actual = resolver.get_class_name(1); + assert_eq!(Some("java/lang/Byte"), actual.as_deref()) + } + + #[test] + fn should_return_full_field_info() { + let resolver = CPoolHelper::new(&vec![ + Empty, + Class { name_index: 2 }, + Utf8 { + value: "TheClass".to_string(), + }, + Fieldref { + class_index: 1, + name_and_type_index: 4, + }, + NameAndType { + name_index: 5, + descriptor_index: 6, + }, + Utf8 { + value: "theField".to_string(), + }, + Utf8 { + value: "I".to_string(), + }, + ]); + + let actual = resolver.get_full_field_info(3); + assert_eq!( + Some(( + "TheClass".to_string(), + "theField".to_string(), + "I".to_string() + )), + actual + ); + } + + #[test] + fn should_return_full_method_info() { + let resolver = CPoolHelper::new(&vec![ + Empty, + Class { name_index: 2 }, + Utf8 { + value: "TheClass".to_string(), + }, + Methodref { + class_index: 1, + name_and_type_index: 4, + }, + NameAndType { + name_index: 5, + descriptor_index: 6, + }, + Utf8 { + value: "theMethod".to_string(), + }, + Utf8 { + value: "()V".to_string(), + }, + ]); + + let actual = resolver.get_full_method_info(3); + assert_eq!( + Some(( + "TheClass".to_string(), + "theMethod".to_string(), + "()V".to_string() + )), + actual + ); + } + + #[test] + fn should_return_name_and_type() { + let resolver = CPoolHelper::new(&vec![ + Empty, + NameAndType { + name_index: 2, + descriptor_index: 3, + }, + Utf8 { + value: "theField".to_string(), + }, + Utf8 { + value: "J".to_string(), + }, + ]); + + let actual = resolver.get_name_and_type(1); + assert_eq!(Some(("theField".to_string(), "J".to_string())), actual); + } + + #[test] + fn should_return_integer() { + let resolver = + CPoolHelper::new(&vec![Empty, Class { name_index: 2 }, Integer { value: 42 }]); + + let actual = resolver.get_integer(2); + assert_eq!(Some(42), actual) + } + + #[test] + fn should_return_long() { + let resolver = CPoolHelper::new(&vec![ + Empty, + Class { name_index: 2 }, + Long { + value: 9_000_000_000, + }, + ]); + + let actual = resolver.get_long(2); + assert_eq!(Some(9_000_000_000), actual) + } +} diff --git a/vm/src/method_area/java_class.rs b/vm/src/method_area/java_class.rs index e3fb9cc1..84666061 100644 --- a/vm/src/method_area/java_class.rs +++ b/vm/src/method_area/java_class.rs @@ -1,21 +1,22 @@ +use crate::method_area::cpool_helper::CPoolHelper; use crate::method_area::field::Field; use crate::method_area::java_method::JavaMethod; -use jclass::class_file::ClassFile; use jdescriptor::TypeDescriptor; use std::cell::RefCell; use std::collections::HashMap; +use std::rc::Rc; #[derive(Debug)] pub(crate) struct JavaClass { pub(crate) methods: Methods, pub(crate) static_fields: Fields, - pub(crate) non_static_fields_descriptors: HashMap, - pub(crate) class_file: ClassFile, + pub(crate) field_descriptors: FieldDescriptors, + cpool_helper: CPoolHelper, } #[derive(Debug)] pub(crate) struct Methods { - pub(crate) method_by_signature: HashMap, + pub(crate) method_by_signature: HashMap>, } #[derive(Debug)] @@ -23,6 +24,17 @@ pub(crate) struct Fields { pub(crate) field_by_name: HashMap>, } +#[derive(Debug)] +pub(crate) struct FieldDescriptors { + pub(crate) descriptor_by_name: HashMap, +} + +impl FieldDescriptors { + pub fn new(descriptor_by_name: HashMap) -> Self { + Self { descriptor_by_name } + } +} + impl Fields { pub fn new(field_by_name: HashMap>) -> Self { Self { field_by_name } @@ -33,20 +45,24 @@ impl JavaClass { pub fn new( methods: Methods, static_fields: Fields, - non_static_fields_descriptors: HashMap, - class_file: ClassFile, + field_descriptors: FieldDescriptors, + cpool_helper: CPoolHelper, ) -> Self { Self { methods, static_fields, - non_static_fields_descriptors, - class_file, + field_descriptors, + cpool_helper, } } + + pub fn cpool_helper(&self) -> &CPoolHelper { + &self.cpool_helper + } } impl Methods { - pub fn new(method_by_signature: HashMap) -> Self { + pub fn new(method_by_signature: HashMap>) -> Self { Self { method_by_signature, } diff --git a/vm/src/method_area/java_method.rs b/vm/src/method_area/java_method.rs index 2059b08e..818eecb5 100644 --- a/vm/src/method_area/java_method.rs +++ b/vm/src/method_area/java_method.rs @@ -31,7 +31,7 @@ impl JavaMethod { StackFrame::new( self.max_locals as usize, self.max_stack as usize, - &self.bytecode, + self.bytecode.clone(), self.class_name.clone(), ) } diff --git a/vm/src/method_area/method_area.rs b/vm/src/method_area/method_area.rs index 2f37c2b8..3580a254 100644 --- a/vm/src/method_area/method_area.rs +++ b/vm/src/method_area/method_area.rs @@ -1,18 +1,32 @@ -use crate::error::Error; -use crate::method_area::java_class::JavaClass; +use crate::error::{Error, ErrorKind}; +use crate::method_area::attributes_helper::AttributesHelper; +use crate::method_area::cpool_helper::CPoolHelper; +use crate::method_area::field::Field; +use crate::method_area::java_class::{FieldDescriptors, Fields, JavaClass, Methods}; use crate::method_area::java_method::JavaMethod; -use crate::util::{get_class_name_by_cpool_class_index, get_cpool_string}; -use jclass::constant_pool::ConstantPool::{Fieldref, Methodref, NameAndType}; +use jclass::class_file::{parse, ClassFile}; +use jclass::fields::{FieldFlags, FieldInfo}; +use jclass::methods::MethodInfo; +use jdescriptor::TypeDescriptor; +use std::cell::RefCell; use std::collections::HashMap; +use std::fs::File; +use std::io::Read; +use std::path::{Path, PathBuf}; +use std::rc::Rc; #[derive(Debug)] pub(crate) struct MethodArea { - pub(crate) loaded_classes: HashMap, + std_dir: String, + pub(crate) loaded_classes: RefCell>>, } impl MethodArea { - pub fn new(loaded_classes: HashMap) -> Self { - Self { loaded_classes } + pub fn new(std_dir: &str) -> Self { + Self { + std_dir: std_dir.to_string(), + loaded_classes: RefCell::new(HashMap::new()), + } } pub fn set_static_field_value( @@ -22,6 +36,7 @@ impl MethodArea { value: Vec, ) -> crate::error::Result<()> { self.loaded_classes + .borrow_mut() .get(class_name) .and_then(|java_class| java_class.static_fields.field_by_name.get(fieldname)) .and_then(|field| { @@ -32,148 +47,188 @@ impl MethodArea { .ok_or(Error::new_execution("Error modifying static field")) } - pub(crate) fn get_method_by_name_signature( + pub(crate) fn get( &self, - class_name: &str, - method_name_signature: &str, - ) -> crate::error::Result<&JavaMethod> { - if let Some(found) = self - .loaded_classes - .get(class_name) - .expect(format!("class {class_name} not found").as_str()) - .methods - .method_by_signature - .get(method_name_signature) - { - return Ok(found); + fully_qualified_class_name: &str, + ) -> crate::error::Result> { + if let Some(java_class) = self.loaded_classes.borrow().get(fully_qualified_class_name) { + return Ok(Rc::clone(java_class)); } - Err(Error::new_constant_pool( - "Error getting method by name from methods map", - )) + //todo: make me thread-safe if move to multithreaded jvm + let java_class = self.load_class_file(fully_qualified_class_name)?; + self.loaded_classes.borrow_mut().insert( + fully_qualified_class_name.to_string(), + Rc::clone(&java_class), + ); + + Ok(java_class) } - pub(crate) fn get_method_by_methodref_cpool_index( + fn load_class_file( &self, - java_class: &JavaClass, - methodref_cpool_index: u16, - ) -> crate::error::Result<(String, String, &JavaMethod)> { - let cpool = java_class.class_file.constant_pool(); - - // Retrieve Methodref from the constant pool - let (class_index, name_and_type_index) = cpool - .get(methodref_cpool_index as usize) - .and_then(|entry| match entry { - Methodref { - class_index, - name_and_type_index, - } => Some((*class_index as usize, *name_and_type_index as usize)), - _ => None, + fully_qualified_class_name: &str, + ) -> crate::error::Result> { + let paths = vec![ + Path::new(&self.std_dir) + .join(fully_qualified_class_name) + .with_extension("class"), + Path::new(fully_qualified_class_name).with_extension("class"), + ]; + + paths + .iter() + .find_map(|file_name| Self::try_open_and_parse(file_name)) + .ok_or_else(|| { + Error::new_execution(&format!("error opening file {fully_qualified_class_name}")) }) + } + + fn try_open_and_parse(path: &PathBuf) -> Option> { + let mut file = File::open(path).ok()?; + let mut buff = Vec::new(); + file.read_to_end(&mut buff).ok()?; + + let class_file = parse(buff.as_slice()) + .map_err(|err| Error::new(ErrorKind::ClassFile(err.to_string()))) + .ok()?; + + Self::to_java_class(class_file) + .map(|(_, java_class)| java_class) + .ok() + } + + fn to_java_class(class_file: ClassFile) -> crate::error::Result<(String, Rc)> { + let cpool_helper = CPoolHelper::new(class_file.constant_pool()); + + let this_class_index = class_file.this_class(); + let class_name = cpool_helper + .get_class_name(this_class_index) .ok_or_else(|| { - Error::new_constant_pool( - format!( - "Invalid Methodref at index {} in class {:?}", - methodref_cpool_index, java_class - ) - .as_str(), - ) + Error::new_constant_pool(&format!( + "Error getting class_name by index={this_class_index}" + )) })?; - // Retrieve class name from the constant pool - let class_name = - get_class_name_by_cpool_class_index(class_index, &java_class.class_file).unwrap(); - - // Retrieve method name and signature from the constant pool - let (method_name_opt, method_signature) = if let NameAndType { - name_index, - descriptor_index, - } = - cpool.get(name_and_type_index).ok_or_else(|| { - Error::new_constant_pool( - format!( - "Invalid NameAndType reference at index {} in class {:?}", - methodref_cpool_index, java_class - ) - .as_str(), - ) - })? { - let name = get_cpool_string(&java_class.class_file, *name_index as usize); - let signature = get_cpool_string(&java_class.class_file, *descriptor_index as usize); - (name, signature) + let super_class_index = class_file.super_class(); + let _super_class_name = if super_class_index > 0 { + cpool_helper + .get_class_name(super_class_index) + .map(Some) + .ok_or_else(|| { + Error::new_constant_pool(&format!( + "Error getting super_class_name by index={super_class_index}" + )) + }) } else { - return Err(Error::new_constant_pool( - format!( - "Expected NameAndType at index {} in class {:?}", - methodref_cpool_index, java_class - ) - .as_str(), - )); - }; - - // Construct method signature and retrieve method - let method_name = method_name_opt.unwrap(); - let full_signature = format!("{}:{}", method_name, method_signature.unwrap()); - let java_method = - self.get_method_by_name_signature(class_name.as_str(), full_signature.as_str())?; - - Ok((class_name, method_name, java_method)) + Ok(None) + }?; + + let interface_indexes = class_file.interfaces(); + let _interface_names = interface_indexes + .iter() + .map(|index| { + cpool_helper.get_class_name(*index).ok_or_else(|| { + Error::new_constant_pool(&format!("Error getting interface by index={index}")) + }) + }) + .collect::>>()?; + + let methods = Self::get_methods(&class_file.methods(), &cpool_helper, &class_name)?; + let (field_descriptors, static_fields) = + Self::get_field_descriptors(&class_file.fields(), &cpool_helper)?; + + Ok(( + class_name.clone(), + Rc::new(JavaClass::new( + methods, + static_fields, + field_descriptors, + cpool_helper, + )), + )) } - pub(crate) fn get_fieldname_by_fieldref_cpool_index( - &self, - java_class: &JavaClass, - fieldref_cpool_index: u16, - ) -> crate::error::Result<(String, String)> { - let cpool = java_class.class_file.constant_pool(); - - // Retrieve Fieldref from the constant pool - let (class_index, name_and_type_index) = cpool - .get(fieldref_cpool_index as usize) - .and_then(|entry| match entry { - Fieldref { - class_index, - name_and_type_index, - } => Some((*class_index as usize, *name_and_type_index as usize)), - _ => None, - }) - .ok_or_else(|| { - Error::new_constant_pool( - format!( - "Invalid Fieldref at index {} in class {:?}", - fieldref_cpool_index, java_class - ) - .as_str(), - ) + fn get_methods( + class_file_methods: &[MethodInfo], + helper: &CPoolHelper, + class_name: &str, + ) -> crate::error::Result { + let mut method_by_signature = HashMap::new(); + + for method_info in class_file_methods.iter() { + let name_index = method_info.name_index(); + let method_name = helper.get_utf8(name_index).ok_or_else(|| { + Error::new_execution(&format!("error getting method name by index {name_index}")) })?; - // Retrieve class name from the constant pool - let class_name = get_class_name_by_cpool_class_index(class_index, &java_class.class_file); - - // Retrieve field name from the constant pool - let field_name = if let NameAndType { - name_index, - descriptor_index: _, - } = cpool.get(name_and_type_index).ok_or_else(|| { - Error::new_constant_pool( - format!( - "Invalid NameAndType reference at index {} in class {:?}", - fieldref_cpool_index, java_class - ) - .as_str(), - ) - })? { - get_cpool_string(&java_class.class_file, *name_index as usize) - } else { - return Err(Error::new_constant_pool( - format!( - "Expected NameAndType at index {} in class {:?}", - fieldref_cpool_index, java_class - ) - .as_str(), - )); - }; - - Ok((class_name.unwrap(), field_name.unwrap())) + let descriptor_index = method_info.descriptor_index(); + let method_signature = helper.get_utf8(descriptor_index).ok_or_else(|| { + Error::new_execution(&format!( + "error getting method signature by index {descriptor_index}" + )) + })?; + + let key = format!("{method_name}:{method_signature}"); + + let attributes_helper = AttributesHelper::new(method_info.attributes()); + let (max_stack, max_locals, code) = attributes_helper.get_code().ok_or_else(|| { + Error::new_execution(&format!("Error getting code attribute for method {key}")) + })?; + + method_by_signature.insert( + key, + Rc::new(JavaMethod::new( + method_signature.parse().map_err(|err| { + Error::new_execution(&format!( + "Error parsing signature {method_signature}: {err}" + )) + })?, + max_stack, + max_locals, + code, + class_name, + )), + ); + } + + Ok(Methods::new(method_by_signature)) + } + + fn get_field_descriptors( + field_infos: &[FieldInfo], + cpool_helper: &CPoolHelper, + ) -> crate::error::Result<(FieldDescriptors, Fields)> { + let mut descriptor_by_name = HashMap::new(); + let mut static_field_by_name = HashMap::new(); + for field_info in field_infos.iter() { + let name_index = field_info.name_index(); + let field_name = cpool_helper.get_utf8(name_index).ok_or_else(|| { + Error::new_execution(&format!("Error getting field name by index {name_index}")) + })?; + + let descriptor_index = field_info.descriptor_index(); + let field_descriptor = cpool_helper.get_utf8(descriptor_index).ok_or_else(|| { + Error::new_execution(&format!( + "Error getting field descriptor by index {descriptor_index}" + )) + })?; + let descriptor: TypeDescriptor = field_descriptor.parse().map_err(|err| { + Error::new_execution(&format!( + "Error parsing field descriptor {field_descriptor}: {err}" + )) + })?; + + descriptor_by_name.insert(field_name.clone(), descriptor.clone()); + + if field_info.access_flags().contains(FieldFlags::ACC_STATIC) { + static_field_by_name.insert(field_name, RefCell::new(Field::new(descriptor))); + } + } + + Ok(( + FieldDescriptors::new(descriptor_by_name), + Fields::new(static_field_by_name), + )) } } diff --git a/vm/src/method_area/mod.rs b/vm/src/method_area/mod.rs index 06a94e5f..17e10e50 100644 --- a/vm/src/method_area/mod.rs +++ b/vm/src/method_area/mod.rs @@ -1,3 +1,5 @@ +mod attributes_helper; +mod cpool_helper; pub(crate) mod field; pub(crate) mod java_class; pub(crate) mod java_method; diff --git a/vm/src/stack/stack_frame.rs b/vm/src/stack/stack_frame.rs index 001de012..bcc61e02 100644 --- a/vm/src/stack/stack_frame.rs +++ b/vm/src/stack/stack_frame.rs @@ -1,16 +1,17 @@ -pub(crate) struct StackFrame<'a> { +#[derive(Clone)] +pub(crate) struct StackFrame { pc: usize, pub locals: Vec, pub(crate) operand_stack: Vec, - bytecode_ref: &'a [u8], + bytecode_ref: Vec, current_class_name: String, } -impl<'a> StackFrame<'a> { +impl<'a> StackFrame { pub fn new( locals_size: usize, stack_size: usize, - bytecode_ref: &'a [u8], + bytecode_ref: Vec, current_class_name: String, ) -> Self { StackFrame { diff --git a/vm/src/util.rs b/vm/src/util.rs deleted file mode 100644 index dbbd6b94..00000000 --- a/vm/src/util.rs +++ /dev/null @@ -1,78 +0,0 @@ -use crate::error::Error; -use crate::method_area::field::Field; -use jclass::class_file::ClassFile; -use jclass::constant_pool::ConstantPool; -use jclass::constant_pool::ConstantPool::{Class, Integer, Utf8}; -use jclass::fields::FieldFlags; -use std::collections::HashMap; - -pub(crate) enum Primitive { - Long(i64), - Double(f64), -} - -pub(crate) fn get_class_name_by_cpool_class_index( - class_index: usize, - class_file: &ClassFile, -) -> Option { - class_file - .constant_pool() - .get(class_index) - .and_then(|cpool| match cpool { - Class { name_index } => Some(*name_index as usize), - _ => None, - }) - .and_then(|index| get_cpool_string(class_file, index)) -} - -pub(crate) fn get_cpool_string(class_file: &ClassFile, index: usize) -> Option { - let constant_pool = class_file.constant_pool(); - - constant_pool.get(index).and_then(|item| match item { - Utf8 { value } => Some(value.clone()), - _ => None, - }) -} - -pub(crate) fn get_cpool_integer(class_file: &ClassFile, index: usize) -> Option { - let constant_pool = class_file.constant_pool(); - - constant_pool.get(index).and_then(|item| match item { - Integer { value } => Some(value.clone()), - _ => None, - }) -} - -pub(crate) fn get_cpool_long_double(class_file: &ClassFile, index: usize) -> Option { - let constant_pool = class_file.constant_pool(); - - constant_pool.get(index).and_then(|item| match item { - ConstantPool::Long { value } => Some(Primitive::Long(*value)), - ConstantPool::Double { value } => Some(Primitive::Double(*value)), - _ => todo!("symbolic reference to a dynamically-computed"), - }) -} - -pub(crate) fn get_fields(class_file: &ClassFile) -> crate::error::Result> { - let result = class_file - .fields() - .iter() - .filter_map(|field| { - if field.access_flags().contains(FieldFlags::ACC_STATIC) { - None - } else { - let field_name = get_cpool_string(class_file, field.name_index() as usize) - .ok_or_else(|| Error::new_constant_pool("Error getting field name")) - .ok()?; - let field_signature = - get_cpool_string(class_file, field.descriptor_index() as usize) - .ok_or_else(|| Error::new_constant_pool("Error getting field signature")) - .ok()?; - - Some((field_name, Field::new(field_signature.parse().unwrap()))) - } - }) - .collect(); - - Ok(result) -} diff --git a/vm/src/vm.rs b/vm/src/vm.rs index 4d88bf01..303419e6 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -1,28 +1,35 @@ -use crate::class_loader::ClassLoader; +use crate::error::Error; use crate::execution_engine::engine::Engine; +use crate::method_area::method_area::MethodArea; #[derive(Debug)] pub struct VM { - class_loader: ClassLoader, + method_area: MethodArea, } impl VM { - pub fn new(class_file_names: Vec<&str>, std_dir: &str) -> crate::error::Result { - let class_loader = ClassLoader::new(class_file_names, std_dir)?; - Ok(Self { class_loader }) + const ENTRY_POINT: &'static str = "main:([Ljava/lang/String;)V"; + + pub fn new(std_dir: &str) -> Self { + Self { + method_area: MethodArea::new(std_dir), + } } - pub fn run(&self, main_class_name: &str) -> crate::error::Result>> { - let main_method = self - .class_loader - .method_area() - .get_method_by_name_signature( - &main_class_name.replace('.', "/"), - "main:([Ljava/lang/String;)V", - )?; + pub fn run(&mut self, main_class_name: &str) -> crate::error::Result>> { + let internal_name = &main_class_name.replace('.', "/"); + let java_class = self.method_area.get(internal_name)?; + + let java_method = java_class + .methods + .method_by_signature + .get(Self::ENTRY_POINT) + .ok_or(Error::new_execution( + format!("main method not found in {main_class_name}").as_str(), + ))?; - let mut engine = Engine::new(&self.class_loader.method_area()); + let mut engine = Engine::new(&self.method_area); - engine.execute(main_method) + engine.execute(java_method) } }