Merge remote-tracking branch 'origin/master' into master

This commit is contained in:
nora 2021-10-25 14:20:05 +02:00
commit 6dd9a0df68
6 changed files with 320 additions and 28 deletions

View file

@ -8,4 +8,5 @@ my goal is not to make a fully working jvm that will run your spring application
it would be amazing if it even managed to run a hello world
## what i have for now:
* (Hopefully) working complete `.class` file parser
* Almost working complete `.class` file parser
* Primitive file info for `.class` files similar to `javap`

View file

@ -1,23 +1,144 @@
#![allow(dead_code)]
struct MethodSignature {
args: Vec<Type>,
return_t: Type,
#[cfg(test)]
mod test;
use std::borrow::Cow;
use std::str::FromStr;
#[derive(Debug)]
pub struct ParseErr(pub Cow<'static, str>);
impl ParseErr {
pub fn str(str: &'static str) -> Self {
Self(Cow::Borrowed(str))
}
pub fn string(str: String) -> Self {
Self(Cow::Owned(str))
}
}
/// A Java type, found in signatures
enum Type {
/// A field descriptor for the type of a field in a class
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
pub struct FieldDescriptor(pub FieldType);
/// The type of a field or method parameter
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
pub enum FieldType {
/// B
Byte,
/// C
Char,
/// D
Double,
/// F
Float,
/// I
Int,
/// J
Long,
/// L `ClassName` ;
Object(String),
/// S
Short,
/// Z
Boolean,
/// [
Array(Box<Self>),
}
/// A method descriptor for the type of a method in a class
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
pub struct MethodDescriptor {
parameters: Vec<FieldType>,
return_: MethodType,
}
/// The type of a method
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
pub enum MethodType {
Some(FieldType),
/// V
Void,
/// B
Boolean,
Byte,
Short,
Int,
Long,
Float,
Double,
Object,
/// [
Array(Box<Type>),
}
impl FromStr for FieldDescriptor {
type Err = ParseErr;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Self(FieldType::from_char_iter(&mut s.chars())?))
}
}
impl FieldType {
/// Consumes as much chars as needed from the char iterator and tries to parse itself
pub fn from_char_iter<I>(chars: &mut I) -> Result<Self, ParseErr>
where
I: Iterator<Item = char>,
{
let first = chars.next().ok_or_else(|| ParseErr::str("Empty string"))?;
Ok(match first {
'B' => Self::Byte,
'C' => Self::Char,
'D' => Self::Double,
'F' => Self::Float,
'I' => Self::Int,
'J' => Self::Long,
'L' => Self::Object({
let mut name = String::with_capacity(32); // we can expect ClassNames to be at least this long
loop {
let char = chars
.next()
.ok_or_else(|| ParseErr::str("Expected ; before end of string"))?;
if char == ';' {
break;
};
name.push(char);
}
name
}),
'S' => Self::Short,
'Z' => Self::Boolean,
'[' => Self::Array(Box::new(Self::from_char_iter(chars)?)),
c => {
return Err(ParseErr::string(format!(
"Invalid char in field descriptor {}",
c
)))
}
})
}
}
impl FromStr for MethodDescriptor {
type Err = ParseErr;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut chars = s.chars().peekable();
if chars.next().ok_or_else(|| ParseErr::str("Empty string"))? != '(' {
return Err(ParseErr::str("Needs to start with '('"));
}
let mut parameters = Vec::new();
loop {
if let Some(')') = chars.peek() {
let _ = chars.next(); // consume the )
break;
}
parameters.push(FieldType::from_char_iter(&mut chars)?);
}
let return_ = if let Some('V') = chars.peek() {
MethodType::Void
} else {
MethodType::Some(FieldType::from_char_iter(&mut chars)?)
};
Ok(Self {
parameters,
return_,
})
}
}

View file

@ -0,0 +1,126 @@
use super::*;
#[test]
fn field_descriptor() {
let descriptors = [
FieldDescriptor::from_str("B").unwrap(),
FieldDescriptor::from_str("C").unwrap(),
FieldDescriptor::from_str("D").unwrap(),
FieldDescriptor::from_str("F").unwrap(),
FieldDescriptor::from_str("I").unwrap(),
FieldDescriptor::from_str("J").unwrap(),
FieldDescriptor::from_str("S").unwrap(),
FieldDescriptor::from_str("Z").unwrap(),
FieldDescriptor::from_str("[B").unwrap(),
FieldDescriptor::from_str("[[Z").unwrap(),
FieldDescriptor::from_str("Ljava/lang/String;").unwrap(),
FieldDescriptor::from_str("[[[Ljava/lang/String;").unwrap(),
];
type FT = FieldType;
let expected_descriptors = [
FieldDescriptor(FT::Byte),
FieldDescriptor(FT::Char),
FieldDescriptor(FT::Double),
FieldDescriptor(FT::Float),
FieldDescriptor(FT::Int),
FieldDescriptor(FT::Long),
FieldDescriptor(FT::Short),
FieldDescriptor(FT::Boolean),
FieldDescriptor(FT::Array(Box::new(FT::Byte))),
FieldDescriptor(FT::Array(Box::new(FT::Array(Box::new(FT::Boolean))))),
FieldDescriptor(FT::Object("java/lang/String".to_string())),
FieldDescriptor(FT::Array(Box::new(FT::Array(Box::new(FT::Array(
Box::new(FT::Object("java/lang/String".to_string())),
)))))),
];
let invalid_descriptors = ["", "Q", "[]", "[", "Ljava/lang/String", "L", "[[[Ljava"];
descriptors
.iter()
.zip(expected_descriptors.iter())
.for_each(|(a, b)| assert_eq!(a, b));
invalid_descriptors
.iter()
.map(|d| FieldDescriptor::from_str(d))
.for_each(|rs| {
if rs.is_ok() {
panic!("Successfully parsed invalid result, {:?}", rs);
}
});
}
#[test]
fn method_descriptor() {
let descriptors = vec![
MethodDescriptor::from_str("()V").unwrap(),
MethodDescriptor::from_str("(B)V").unwrap(),
MethodDescriptor::from_str("([ZZ)Ljava/lang/Object;").unwrap(),
MethodDescriptor::from_str("(IDLjava/lang/Thread;)Ljava/lang/Object;").unwrap(),
MethodDescriptor::from_str("(BBBBBBBBBB)B").unwrap(),
MethodDescriptor::from_str("()Z").unwrap(),
];
type FT = FieldType;
let expected_descriptors = [
MethodDescriptor {
parameters: vec![],
return_: MethodType::Void,
},
MethodDescriptor {
parameters: vec![FT::Byte],
return_: MethodType::Void,
},
MethodDescriptor {
parameters: vec![FT::Array(Box::new(FT::Boolean)), FT::Boolean],
return_: MethodType::Some(FT::Object("java/lang/Object".to_string())),
},
MethodDescriptor {
parameters: vec![
FT::Int,
FT::Double,
FT::Object("java/lang/Thread".to_string()),
],
return_: MethodType::Some(FT::Object("java/lang/Object".to_string())),
},
MethodDescriptor {
parameters: vec![
FT::Byte,
FT::Byte,
FT::Byte,
FT::Byte,
FT::Byte,
FT::Byte,
FT::Byte,
FT::Byte,
FT::Byte,
FT::Byte,
],
return_: MethodType::Some(FT::Byte),
},
MethodDescriptor {
parameters: vec![],
return_: MethodType::Some(FT::Boolean),
},
];
let invalid_descriptors = ["()", "(V)V", ")V", "(;)Z", "(java/lang/StringZ)", "V"];
invalid_descriptors
.iter()
.map(|d| MethodDescriptor::from_str(d))
.for_each(|rs| {
if rs.is_ok() {
panic!("Successfully parsed invalid result, {:?}", rs);
}
});
descriptors
.iter()
.zip(expected_descriptors.iter())
.for_each(|(a, b)| assert_eq!(a, b));
}

View file

@ -165,7 +165,6 @@ impl Parse for ClassFile {
impl Parse for CpInfo {
fn parse(data: &mut Data, cp: &[CpInfo]) -> Result<Self> {
let tag = data.u1()?;
dbg!(tag);
Ok(match tag {
7 => Self {
@ -261,6 +260,13 @@ impl Parse for CpInfo {
descriptor_index: data.cp(cp)?,
}),
},
17 => Self {
tag,
inner: CpInfoInner::Dynamic(cp_info::Dynamic {
bootstrap_method_attr_index: data.u2()?,
name_and_type_index: data.cp(cp)?,
}),
},
18 => Self {
tag,
inner: CpInfoInner::InvokeDynamic(cp_info::InvokeDynamic {
@ -268,6 +274,18 @@ impl Parse for CpInfo {
name_and_type_index: data.cp(cp)?,
}),
},
19 => Self {
tag,
inner: CpInfoInner::Module(cp_info::Module {
name_index: data.cp(cp)?,
}),
},
20 => Self {
tag,
inner: CpInfoInner::Package(cp_info::Package {
name_index: data.cp(cp)?,
}),
},
_ => return Err(ParseErr(format!("Invalid CPInfo tag: {}", tag))),
})
}

View file

@ -159,7 +159,6 @@ impl ValidateCpInfo for CpInfoInner {
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
pub struct Class {
/// Entry must be `Utf8`
pub name_index: FromPool<Utf8>,
}
@ -167,7 +166,6 @@ pub struct Class {
pub struct Fieldref {
/// May be a class or interface type
pub class_index: FromPool<Class>,
/// Entry must be `NameAndType`
pub name_and_type_index: FromPool<NameAndType>,
}
@ -175,7 +173,6 @@ pub struct Fieldref {
pub struct MethodRef {
/// Must be a class type
pub class_index: FromPool<Class>,
/// Entry must be `NameAndType`
pub name_and_type_index: FromPool<NameAndType>,
}
@ -183,13 +180,11 @@ pub struct MethodRef {
pub struct InterfaceMethodref {
/// Must be an interface type
pub class_index: FromPool<Class>,
/// Entry must be `NameAndType`
pub name_and_type_index: FromPool<NameAndType>,
}
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
pub struct String {
/// Entry must be `Utf8`
pub string_index: FromPool<Utf8>,
}
@ -226,9 +221,7 @@ pub struct Double {
/// Any field or method, without the class it belongs to
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
pub struct NameAndType {
/// Entry must be `Utf8`
pub name_index: FromPool<Utf8>,
/// Entry must be `Utf8`
pub descriptor_index: FromPool<Utf8>,
}
@ -256,18 +249,33 @@ pub enum MethodHandleIndex {
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
pub struct MethodType {
/// Entry must be `Utf8`
pub descriptor_index: FromPool<Utf8>,
}
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
pub struct Dynamic {
/// Must be a valid index into the `bootstrap_methods` array of the bootstrap method table of this class field
pub bootstrap_method_attr_index: u2,
pub name_and_type_index: FromPool<NameAndType>,
}
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
pub struct InvokeDynamic {
/// Must be a valid index into the `bootstrap_methods` array of the bootstrap method table of this class field
pub bootstrap_method_attr_index: u2,
/// Entry must `NameAndType`
pub name_and_type_index: FromPool<NameAndType>,
}
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
pub struct Module {
pub name_index: FromPool<Utf8>,
}
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
pub struct Package {
pub name_index: FromPool<Utf8>,
}
// default implementations
impl_try_from_cp!(
@ -283,7 +291,10 @@ impl_try_from_cp!(
NameAndType,
MethodHandle,
MethodType,
InvokeDynamic
Dynamic,
InvokeDynamic,
Module,
Package
);
impl ValidateCpInfo for Utf8 {

View file

@ -2,6 +2,8 @@
//! The models for a .class file
//!
//! [The .class specs](https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html)
//!
//! todo poart to [SE16](https://docs.oracle.com/javase/specs/jvms/se16/html/jvms-4.html)
#![allow(dead_code)]
/// All of the Constants in the Constant Pool
@ -74,7 +76,10 @@ pub enum CpInfoInner {
Utf8(cp_info::Utf8),
MethodHandle(cp_info::MethodHandle),
MethodType(cp_info::MethodType),
Dynamic(cp_info::Dynamic),
InvokeDynamic(cp_info::InvokeDynamic),
Module(cp_info::Module),
Package(cp_info::Package),
}
/// Information about a field
@ -222,6 +227,14 @@ pub enum AttributeInfoInner {
BootstrapMethods {
bootstrap_methods: Vec<BootstrapMethod>,
},
// todo
MethodParameters,
Module,
ModulePackages,
ModuleMainClass,
NestHost,
NestMembers,
Record,
}
/// An exception handler in the JVM bytecode array
@ -450,6 +463,8 @@ pub enum ClassAccessFlag {
Annotation = 0x2000,
/// Declared as an enum type.
Enum = 0x4000,
/// Is a module, not a class or interface.
MODULE = 0x8000,
}
/// Access Flags of a method