use crate::constant_pool::parser::parse_attribute_info;
use crate::raw_java_class::RawAttributeInfo;
use crate::structures::fully_qualified_name::FQName;
use crate::utility::match_as;
use crate::{ConstantPoolInfo, HasAttributes};
use crate::{JavaClass, Signature};
use byteorder::ByteOrder;
use nom::bytes::complete::take;
use nom::combinator::{complete, flat_map, map};
use nom::multi::count;
use nom::number::complete::{be_u16, be_u32};
use nom::sequence::tuple;
use nom::{Finish, IResult};
use std::collections::HashMap;
use std::fmt::{Debug, Formatter};
use std::path::Path;
#[derive(Debug, Clone)]
pub struct Attribute<'a> {
attribute_name: &'a str,
kind: AttributeKind<'a>,
}
#[derive(Debug, Clone)]
pub enum AttributeKind<'a> {
SourceFile(&'a Path),
Signature(Signature<'a>),
Code(Code<'a>),
LineNumberTable(LineNumberTable),
Deprecated,
Unknown(&'a [u8]),
}
impl<'a> Attribute<'a> {
pub(crate) fn new(
class: &'a JavaClass,
attribute_name: &'a str,
bytes: &'a [u8],
) -> Result<Self, ResolveAttributeError> {
let error = || ResolveAttributeError::new(attribute_name);
let kind: AttributeKind = match attribute_name {
"SourceFile" => {
let index = byteorder::BigEndian::read_u16(bytes);
let utf8 = class.get_string(index).ok_or(error())?;
AttributeKind::SourceFile(Path::new(utf8))
}
"Signature" => {
let index = byteorder::BigEndian::read_u16(bytes);
let utf8 = class.get_string(index).ok_or(error())?;
let signature = Signature::new(utf8).map_err(|_| error())?;
AttributeKind::Signature(signature)
}
"Code" => {
let (_, code) = parse_code_attr(bytes, class).finish().unwrap();
AttributeKind::Code(code)
}
"LineNumberTable" => {
let parser = |bytes| -> IResult<&[u8], Vec<(u16, u16)>> {
flat_map(be_u16, |length: u16| {
count(tuple((be_u16, be_u16)), length as usize)
})(bytes)
};
let (_, lines) = parser(bytes).finish().unwrap();
AttributeKind::LineNumberTable(LineNumberTable {
line_number_table: lines.into_boxed_slice(),
})
}
"Deprecated" => AttributeKind::Deprecated,
_ => AttributeKind::Unknown(bytes),
};
Ok(Self {
attribute_name,
kind,
})
}
pub fn attribute_name(&self) -> &'a str {
self.attribute_name
}
pub fn kind(&self) -> &AttributeKind<'a> {
&self.kind
}
}
#[derive(Debug, thiserror::Error)]
#[error("An error occurred while resolving attribute {0}")]
pub struct ResolveAttributeError(String);
impl ResolveAttributeError {
pub(crate) fn new<S: AsRef<str>>(string: S) -> Self {
Self(string.as_ref().to_string())
}
}
#[derive(Clone)]
pub struct Code<'a> {
class: &'a JavaClass,
max_stack: u16,
max_locals: u16,
code: &'a [u8],
exception_table: Vec<Exception<'a>>,
attributes: Box<[RawAttributeInfo]>,
}
impl<'a> Code<'a> {
pub fn max_stack(&self) -> u16 {
self.max_stack
}
pub fn max_locals(&self) -> u16 {
self.max_locals
}
pub fn code(&self) -> &'a [u8] {
self.code
}
pub fn exception_table(&self) -> &[Exception<'a>] {
&self.exception_table[..]
}
}
impl HasAttributes for Code<'_> {
type Iter<'a> = <Vec<Attribute<'a>> as IntoIterator>::IntoIter where Self: 'a;
fn attributes<'a>(&'a self) -> Self::Iter<'a> {
self.attributes
.iter()
.map(|raw| {
self.class
.create_attribute(raw.attribute_name_index, &raw.info)
.unwrap()
})
.collect::<Vec<_>>()
.into_iter()
}
}
impl Debug for Code<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Code")
.field("max_stack", &self.max_stack)
.field("max_locals", &self.max_locals)
.field("code", &self.code)
.field("exception_table", &self.exception_table)
.field("attributes", &self.attributes().collect::<Vec<_>>())
.finish()
}
}
#[derive(Debug, Clone)]
pub struct Exception<'a> {
start_pc: u16,
end_pc: u16,
handler_pc: u16,
catch_type: Option<&'a FQName>,
}
impl<'a> Exception<'a> {
pub fn start_pc(&self) -> u16 {
self.start_pc
}
pub fn end_pc(&self) -> u16 {
self.end_pc
}
pub fn handler_pc(&self) -> u16 {
self.handler_pc
}
pub fn catch_type(&self) -> Option<&FQName> {
self.catch_type
}
}
fn parse_code_attr<'a>(info: &'a [u8], class: &'a JavaClass) -> IResult<&'a [u8], Code<'a>> {
map(
complete(tuple((
be_u16,
be_u16,
flat_map(be_u32, |code_length: u32| take(code_length)),
flat_map(be_u16, |exception_table_length: u16| {
count(
|b| parse_exception(b, class),
exception_table_length as usize,
)
}),
flat_map(be_u16, |attribute_length: u16| {
count(parse_attribute_info, attribute_length as usize)
}),
))),
|(max_stack, max_locals, code, exception_table, attributes)| Code {
class,
max_stack,
max_locals,
code,
exception_table: exception_table,
attributes: attributes.into_boxed_slice(),
},
)(info)
}
fn parse_exception<'a>(bytes: &'a [u8], class: &'a JavaClass) -> IResult<&'a [u8], Exception<'a>> {
map(
tuple((be_u16, be_u16, be_u16, be_u16)),
|(start_pc, end_pc, handler_pc, catch_type_index)| Exception {
start_pc,
end_pc,
handler_pc,
catch_type: if catch_type_index == 0 {
None
} else {
class
.get_at_index(catch_type_index)
.and_then(|info| match_as!(utf; ConstantPoolInfo::Utf8(utf) = info))
.map(|utf8| FQName::new(utf8))
},
},
)(bytes)
}
#[derive(Clone)]
pub struct LineNumberTable {
line_number_table: Box<[(u16, u16)]>,
}
impl LineNumberTable {
pub fn pc_to_line(&self, pc: u16) -> Option<u16> {
let mut output = None;
for &(start_pc, line_number) in &self.line_number_table[..] {
if pc > start_pc {
break;
} else {
output = Some(line_number);
}
}
output
}
}
impl Debug for LineNumberTable {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
self.line_number_table
.iter()
.copied()
.collect::<HashMap<_, _>>()
.fmt(f)
}
}