From 54ca19a45a27388d608abe4511279f03b0bf6e1d Mon Sep 17 00:00:00 2001 From: Nilstrieb Date: Sat, 28 Aug 2021 23:38:37 +0200 Subject: [PATCH] Field and Method descriptor parsing with tests --- crates/class-struct/src/lib.rs | 153 ++++++++++++++++++++++++++++---- crates/class-struct/src/test.rs | 126 ++++++++++++++++++++++++++ 2 files changed, 263 insertions(+), 16 deletions(-) create mode 100644 crates/class-struct/src/test.rs diff --git a/crates/class-struct/src/lib.rs b/crates/class-struct/src/lib.rs index 05cf7a5..3e82d2a 100644 --- a/crates/class-struct/src/lib.rs +++ b/crates/class-struct/src/lib.rs @@ -1,23 +1,144 @@ #![allow(dead_code)] -struct MethodSignature { - args: Vec, - 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), +} + +/// A method descriptor for the type of a method in a class +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub struct MethodDescriptor { + parameters: Vec, + 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), +} + +impl FromStr for FieldDescriptor { + type Err = ParseErr; + + fn from_str(s: &str) -> Result { + 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(chars: &mut I) -> Result + where + I: Iterator, + { + 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 { + 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_, + }) + } } diff --git a/crates/class-struct/src/test.rs b/crates/class-struct/src/test.rs new file mode 100644 index 0000000..d858920 --- /dev/null +++ b/crates/class-struct/src/test.rs @@ -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)); +}