Skip to content

Commit

Permalink
Introducing VM with support of basic stack operations
Browse files Browse the repository at this point in the history
  • Loading branch information
hextriclosan committed Aug 24, 2024
1 parent 1f1945b commit 6a1053a
Show file tree
Hide file tree
Showing 28 changed files with 965 additions and 2 deletions.
10 changes: 10 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,8 @@ version = "0.1.0"
edition = "2021"

[workspace]
members = ["jclass"]
members = ["jclass", "vm"]

[dependencies]
vm = {path = "vm"}

15 changes: 14 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
@@ -1 +1,14 @@
fn main() {}
use vm::vm::VM;

fn main() {
match VM::new("tests/test_data/Sub.class") {
Ok(vm) => {
println!("{vm:#?}");
vm.run().expect("error running vm");
}
Err(err) => {
eprintln!("Error: {err}");
}
}

}
37 changes: 37 additions & 0 deletions tests/integration_test.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@

use vm::vm::VM;

#[test]
fn should_do_adding() {
let vm = VM::new("tests/test_data/Adder.class").unwrap();
let last_frame_value = vm.run().unwrap();
assert_eq!(55, last_frame_value.unwrap())
}

#[test]
fn should_do_subtraction() {
let vm = VM::new("tests/test_data/Sub.class").unwrap();
let last_frame_value = vm.run().unwrap();
assert_eq!(-999, last_frame_value.unwrap())
}

#[test]
fn should_do_extreme_stack_operations() {
let vm = VM::new("tests/test_data/ExtremeStackTest.class").unwrap();
let last_frame_value = vm.run().unwrap();
assert_eq!(454, last_frame_value.unwrap())
}

#[test]
fn should_do_calculate_fibonacci_iteratively() {
let vm = VM::new("tests/test_data/FibonacciIterative.class").unwrap();
let last_frame_value = vm.run().unwrap();
assert_eq!(55, last_frame_value.unwrap())
}

#[test]
fn should_do_calculate_fibonacci_recursively() {
let vm = VM::new("tests/test_data/FibonacciRecursive.class").unwrap();
let last_frame_value = vm.run().unwrap();
assert_eq!(55, last_frame_value.unwrap())
}
Binary file added tests/test_data/Adder.class
Binary file not shown.
13 changes: 13 additions & 0 deletions tests/test_data/Adder.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//javac -g -parameters Adder.java

public class Adder {

public static void main(String[] args) {
int result = add(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
}

private static int add(int v1, int v2, int v3, int v4, int v5, int v6, int v7, int v8, int v9, int v10) {
return v1 + v2 + v3 + v4 + v5 + v6 + v7 + v8 + v9 + v10;
}

}
Binary file added tests/test_data/ExtremeStackTest.class
Binary file not shown.
69 changes: 69 additions & 0 deletions tests/test_data/ExtremeStackTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@

public class ExtremeStackTest {

public static void main(String[] args) {
// Test nested operations and method calls
int nestedResult = nestedCalculations(10);
//System.out.println("nestedResult=" + nestedResult);

// Test loop operations with complex conditions
int loopResult = complexLoop(5);
//System.out.println("loopResult=" + loopResult);

// Test combined recursive methods
int combinedRecursionResult = factorialPlusFibonacci(5);
//System.out.println("combinedRecursionResult=" + combinedRecursionResult);

int result = nestedResult + loopResult + combinedRecursionResult; // 454
//System.out.println("result=" + result);
}

// Method with nested operations and method calls
private static int nestedCalculations(int n) {
int result = 0;
for (int i = 0; i < n; i++) {
result += multiplyAndAdd(i, n - i);
}
return result * 2 - n; // Final operation after loop
}

// Helper method for nestedCalculations
private static int multiplyAndAdd(int x, int y) {
return (x * y) + (x - y); // Simple arithmetic operations
}

// Method with loop, conditions, and nested operations
private static int complexLoop(int n) {
int result = 1;
for (int i = 1; i <= n; i++) {
if (i % 2 == 0) {
result *= i; // Multiply for even numbers
} else {
result += i; // Add for odd numbers
}
result -= (i % 3 == 0) ? 1 : 0; // Subtract 1 if divisible by 3
}
return result;
}

// Method combining recursion from factorial and Fibonacci
private static int factorialPlusFibonacci(int n) {
return factorial(n) + fibonacci(n);
}

// Recursive method to calculate factorial
private static int factorial(int n) {
if (n <= 1) {
return 1;
}
return n * factorial(n - 1);
}

// Recursive method to calculate the nth Fibonacci number
private static int fibonacci(int n) {
if (n <= 1) {
return n;
}
return fibonacci(n - 1) + fibonacci(n - 2);
}
}
Binary file added tests/test_data/FibonacciIterative.class
Binary file not shown.
25 changes: 25 additions & 0 deletions tests/test_data/FibonacciIterative.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@


public class FibonacciIterative {

public static void main(String[] args) {
int n = 10; // We want the 10th Fibonacci number
int result = fibonacci(n);
}

private static int fibonacci(int n) {
if (n == 0 || n == 1) {
return n;
}

int prev = 0;
int curr = 1;
for (int i = 2; i <= n; i++) {
int next = prev + curr;
prev = curr;
curr = next;
}

return curr;
}
}
Binary file added tests/test_data/FibonacciRecursive.class
Binary file not shown.
17 changes: 17 additions & 0 deletions tests/test_data/FibonacciRecursive.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@


public class FibonacciRecursive {

public static void main(String[] args) {
int n = 10; // We want the 10th Fibonacci number
int result = fibonacci(n);
}

private static int fibonacci(int n) {
if (n <= 1) {
return n;
}
return fibonacci(n - 1) + fibonacci(n - 2);
}

}
Binary file added tests/test_data/Sub.class
Binary file not shown.
10 changes: 10 additions & 0 deletions tests/test_data/Sub.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
//javac -g -parameters Adder.java

public class Sub {
public static void main(String[] args) {
int first = 1;
int second = 1000;
int result = first - second;
}
}

7 changes: 7 additions & 0 deletions vm/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[package]
name = "vm"
version = "0.1.0"
edition = "2021"

[dependencies]
jclass = {path = "../jclass"}
111 changes: 111 additions & 0 deletions vm/src/class_loader.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
use std::collections::HashMap;
use std::fs::File;
use std::io::Read;
use jclass::attributes::Attribute;
use jclass::class_file::{parse, ClassFile};
use jclass::constant_pool::ConstantPool::{Methodref, NameAndType, Utf8};
use jclass::attributes::Attribute::Code;
use crate::error::{Result, Error, ErrorKind};
use crate::method_area::java_class::{JavaClass, Methods};
use crate::method_area::java_method::JavaMethod;
use crate::method_area::method_area::MethodArea;
use crate::method_area::signature::Signature;

#[derive(Debug)]
pub struct ClassLoader {
method_area: MethodArea,
}

impl ClassLoader {
pub(crate) fn new(class_file_name: &str) -> Result<Self> {
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())))?;

let java_class = Self::to_java_class(class_file)?;

Ok(Self { method_area: MethodArea::new(java_class) })
}

fn to_java_class(class_file: ClassFile) -> Result<JavaClass> {
let methods = Self::get_methods(&class_file)?;

Ok(JavaClass::new(methods))
}

fn get_methods(class_file: &ClassFile) -> Result<Methods> {
let methods = class_file.methods();
let mut methodsignature_by_cpoolindex: HashMap<u16, String> = HashMap::new();
let mut method_by_signature: HashMap<String, JavaMethod> = HashMap::new();

for method in methods.iter() {
let method_name = Self::get_cpool_string(class_file, method.name_index() as usize)
.ok_or(Error::new_constant_pool("Error getting method name"))?;
let method_signature = Self::get_cpool_string(class_file, method.descriptor_index() as usize)
.ok_or(Error::new_constant_pool("Error getting method method_signature"))?;
let code = Self::get_cpool_code_attribute(method.attributes())
.ok_or(Error::new_constant_pool("Error getting method code"))?;
let key_signature = method_name + ":" + method_signature.as_str();

method_by_signature.insert(key_signature.clone(), JavaMethod::new(Signature::from_str(method_signature.as_str())?, code.0, code.1, code.2));

let cpool_index = Self::get_cpool_method_index(class_file, method.name_index(), method.descriptor_index());
if let Some(index) = cpool_index {
methodsignature_by_cpoolindex.insert(index, key_signature);
}
}

Ok(Methods::new(methodsignature_by_cpoolindex, method_by_signature))
}

fn get_cpool_string(class_file: &ClassFile, index: usize) -> Option<String> {
let constant_pool = class_file.constant_pool();

constant_pool.get(index)
.and_then(|item| match item {
Utf8 { value } => Some(value.clone()),
_ => None
})
}

fn get_cpool_method_index(class_file: &ClassFile, name_index_to_find: u16, signature_index: u16) -> Option<u16> {
let constant_pool = class_file.constant_pool();

let found_name_and_type_index = constant_pool.iter()
.enumerate()
.find_map(|index| if let NameAndType { name_index, descriptor_index } = *index.1 {
if name_index == name_index_to_find && descriptor_index == signature_index {
Some(index.0)
} else { None }
} else { None })? as u16;

let this_class_index = class_file.this_class();

constant_pool.iter()
.enumerate()
.find_map(|index| if let Methodref { class_index, name_and_type_index } = *index.1 {
if class_index == this_class_index && name_and_type_index == found_name_and_type_index {
Some(index.0 as u16)
} else { None }
} else { None })
}

fn get_cpool_code_attribute(attributes: &Vec<Attribute>) -> Option<(u16, u16, Vec<u8>)> {
attributes.iter()
.find_map(|item| {
if let Code { max_stack, max_locals, code, .. } = item {
Some((*max_stack, *max_locals, code.clone()))
} else {
None
}
})
}

pub fn method_area(&self) -> &MethodArea {
&self.method_area
}
}
60 changes: 60 additions & 0 deletions vm/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
use std::{io, result};
use std::fmt::{Debug, Display, Formatter};
use crate::error::ErrorKind::{ClassFile, ConstantPool, Execution, Io};

pub type Result<T> = result::Result<T, Error>;

#[derive(Debug)]
pub struct Error(Box<ErrorKind>);

impl Error {
pub(crate) fn new(error_kind: ErrorKind) -> Self {
Self(Box::new(error_kind))
}

pub(crate) fn new_constant_pool(descr: &str) -> Self {
Self::new(ConstantPool(String::from(descr)))
}

pub(crate) fn new_execution(descr: &str) -> Self {
Self::new(Execution(String::from(descr)))
}

pub fn kind(&self) -> &ErrorKind {
&self.0
}

pub fn into_kind(self) -> ErrorKind {
*self.0
}
}

impl Display for Error {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match &*self.0 {
Io(err) => write!(f, "I/O Error: {err}"),
ClassFile(err) => write!(f, "ClassFile Error: {err}"),
ConstantPool(descr) => write!(f, "ConstantPool Error: {descr}"),
Execution(descr) => write!(f, "Execution Error: {descr}"),

ErrorKind::__Nonexhaustive => unreachable!(),
}
}
}

impl From<io::Error> for Error {
fn from(error: io::Error) -> Self {
Error::new(Io(error))
}
}

#[derive(Debug)]
pub enum ErrorKind {
Io(io::Error),
ClassFile(String),
ConstantPool(String),
Execution(String),

#[doc(hidden)]
__Nonexhaustive,
}
Loading

0 comments on commit 6a1053a

Please sign in to comment.