use byteorder::{BigEndian, ReadBytesExt};
use std::fmt;
use std::fs::File;
use std::io::{self, Read};
use std::path::Path;
const CLASS_MAGIC: u32 = 0xCAFEBABE;
#[derive(Debug, Clone)]
pub struct ClassFile {
pub magic: u32,
pub minor_version: u16,
pub major_version: u16,
pub constant_pool: Vec<ConstantPoolEntry>,
pub access_flags: u16,
pub this_class: u16,
pub super_class: u16,
pub interfaces: Vec<u16>,
pub fields: Vec<FieldInfo>,
pub methods: Vec<MethodInfo>,
pub attributes: Vec<AttributeInfo>,
}
#[derive(Debug, Clone)]
pub enum ConstantPoolEntry {
ConstantClass {
name_index: u16,
},
ConstantFieldref {
class_index: u16,
name_and_type_index: u16,
},
ConstantMethodref {
class_index: u16,
name_and_type_index: u16,
},
ConstantInterfaceMethodref {
class_index: u16,
name_and_type_index: u16,
},
ConstantString {
string_index: u16,
},
ConstantInteger {
bytes: i32,
},
ConstantFloat {
bytes: f32,
},
ConstantLong {
bytes: i64,
},
ConstantDouble {
bytes: f64,
},
ConstantNameAndType {
name_index: u16,
descriptor_index: u16,
},
ConstantUtf8 {
bytes: Vec<u8>,
},
ConstantMethodHandle {
reference_kind: u8,
reference_index: u16,
},
ConstantMethodType {
descriptor_index: u16,
},
ConstantInvokeDynamic {
bootstrap_method_attr_index: u16,
name_and_type_index: u16,
},
}
#[derive(Debug, Clone)]
pub struct FieldInfo {
pub access_flags: u16,
pub name_index: u16,
pub descriptor_index: u16,
pub attributes: Vec<AttributeInfo>,
}
#[derive(Debug, Clone)]
pub struct MethodInfo {
pub access_flags: u16,
pub name_index: u16,
pub descriptor_index: u16,
pub attributes: Vec<AttributeInfo>,
}
#[derive(Debug, Clone)]
pub struct AttributeInfo {
pub attribute_name_index: u16,
pub info: Vec<u8>,
}
#[derive(Debug)]
pub enum ParseError {
IoError(io::Error),
InvalidMagic(u32),
InvalidConstantPoolTag(u8),
InvalidAttributeLength,
}
impl From<io::Error> for ParseError {
fn from(err: io::Error) -> Self {
ParseError::IoError(err)
}
}
impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ParseError::IoError(e) => write!(f, "IO error: {}", e),
ParseError::InvalidMagic(magic) => write!(
f,
"Invalid magic number: 0x{:08X} (expected 0xCAFEBABE)",
magic
),
ParseError::InvalidConstantPoolTag(tag) => {
write!(f, "Invalid constant pool tag: {}", tag)
}
ParseError::InvalidAttributeLength => write!(f, "Invalid attribute length"),
}
}
}
impl ClassFile {
pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self, ParseError> {
let mut file = File::open(path)?;
let mut data = Vec::new();
file.read_to_end(&mut data)?;
Self::parse(&data)
}
pub fn parse(data: &[u8]) -> Result<Self, ParseError> {
let mut cursor = io::Cursor::new(data);
let magic = cursor.read_u32::<BigEndian>()?;
if magic != CLASS_MAGIC {
return Err(ParseError::InvalidMagic(magic));
}
let minor_version = cursor.read_u16::<BigEndian>()?;
let major_version = cursor.read_u16::<BigEndian>()?;
let constant_pool_count = cursor.read_u16::<BigEndian>()?;
let mut constant_pool = Vec::with_capacity(constant_pool_count as usize);
constant_pool.push(ConstantPoolEntry::ConstantUtf8 { bytes: Vec::new() });
for _ in 1..constant_pool_count {
let tag = cursor.read_u8()?;
let entry = Self::read_constant_pool_entry(&mut cursor, tag)?;
constant_pool.push(entry);
if matches!(tag, 5 | 6) {
constant_pool.push(ConstantPoolEntry::ConstantUtf8 { bytes: Vec::new() });
}
}
let access_flags = cursor.read_u16::<BigEndian>()?;
let this_class = cursor.read_u16::<BigEndian>()?;
let super_class = cursor.read_u16::<BigEndian>()?;
let interfaces_count = cursor.read_u16::<BigEndian>()?;
let mut interfaces = Vec::with_capacity(interfaces_count as usize);
for _ in 0..interfaces_count {
interfaces.push(cursor.read_u16::<BigEndian>()?);
}
let fields_count = cursor.read_u16::<BigEndian>()?;
let mut fields = Vec::with_capacity(fields_count as usize);
for _ in 0..fields_count {
fields.push(Self::read_field_info(&mut cursor)?);
}
let methods_count = cursor.read_u16::<BigEndian>()?;
let mut methods = Vec::with_capacity(methods_count as usize);
for _ in 0..methods_count {
methods.push(Self::read_method_info(&mut cursor)?);
}
let attributes_count = cursor.read_u16::<BigEndian>()?;
let mut attributes = Vec::with_capacity(attributes_count as usize);
for _ in 0..attributes_count {
attributes.push(Self::read_attribute_info(&mut cursor)?);
}
Ok(ClassFile {
magic,
minor_version,
major_version,
constant_pool,
access_flags,
this_class,
super_class,
interfaces,
fields,
methods,
attributes,
})
}
fn read_constant_pool_entry(
cursor: &mut io::Cursor<&[u8]>,
tag: u8,
) -> Result<ConstantPoolEntry, ParseError> {
match tag {
1 => {
let len = cursor.read_u16::<BigEndian>()? as usize;
let mut bytes = vec![0u8; len];
cursor.read_exact(&mut bytes)?;
Ok(ConstantPoolEntry::ConstantUtf8 { bytes })
}
3 => {
let bytes = cursor.read_i32::<BigEndian>()?;
Ok(ConstantPoolEntry::ConstantInteger { bytes })
}
4 => {
let bytes = cursor.read_u32::<BigEndian>()?;
Ok(ConstantPoolEntry::ConstantFloat {
bytes: f32::from_bits(bytes),
})
}
5 => {
let bytes = cursor.read_i64::<BigEndian>()?;
Ok(ConstantPoolEntry::ConstantLong { bytes })
}
6 => {
let bytes = cursor.read_u64::<BigEndian>()?;
Ok(ConstantPoolEntry::ConstantDouble {
bytes: f64::from_bits(bytes),
})
}
7 => {
let name_index = cursor.read_u16::<BigEndian>()?;
Ok(ConstantPoolEntry::ConstantClass { name_index })
}
8 => {
let string_index = cursor.read_u16::<BigEndian>()?;
Ok(ConstantPoolEntry::ConstantString { string_index })
}
9 => {
let class_index = cursor.read_u16::<BigEndian>()?;
let name_and_type_index = cursor.read_u16::<BigEndian>()?;
Ok(ConstantPoolEntry::ConstantFieldref {
class_index,
name_and_type_index,
})
}
10 => {
let class_index = cursor.read_u16::<BigEndian>()?;
let name_and_type_index = cursor.read_u16::<BigEndian>()?;
Ok(ConstantPoolEntry::ConstantMethodref {
class_index,
name_and_type_index,
})
}
11 => {
let class_index = cursor.read_u16::<BigEndian>()?;
let name_and_type_index = cursor.read_u16::<BigEndian>()?;
Ok(ConstantPoolEntry::ConstantInterfaceMethodref {
class_index,
name_and_type_index,
})
}
12 => {
let name_index = cursor.read_u16::<BigEndian>()?;
let descriptor_index = cursor.read_u16::<BigEndian>()?;
Ok(ConstantPoolEntry::ConstantNameAndType {
name_index,
descriptor_index,
})
}
15 => {
let reference_kind = cursor.read_u8()?;
let reference_index = cursor.read_u16::<BigEndian>()?;
Ok(ConstantPoolEntry::ConstantMethodHandle {
reference_kind,
reference_index,
})
}
16 => {
let descriptor_index = cursor.read_u16::<BigEndian>()?;
Ok(ConstantPoolEntry::ConstantMethodType { descriptor_index })
}
18 => {
let bootstrap_method_attr_index = cursor.read_u16::<BigEndian>()?;
let name_and_type_index = cursor.read_u16::<BigEndian>()?;
Ok(ConstantPoolEntry::ConstantInvokeDynamic {
bootstrap_method_attr_index,
name_and_type_index,
})
}
_ => Err(ParseError::InvalidConstantPoolTag(tag)),
}
}
fn read_field_info(cursor: &mut io::Cursor<&[u8]>) -> Result<FieldInfo, ParseError> {
let access_flags = cursor.read_u16::<BigEndian>()?;
let name_index = cursor.read_u16::<BigEndian>()?;
let descriptor_index = cursor.read_u16::<BigEndian>()?;
let attributes_count = cursor.read_u16::<BigEndian>()?;
let mut attributes = Vec::with_capacity(attributes_count as usize);
for _ in 0..attributes_count {
attributes.push(Self::read_attribute_info(cursor)?);
}
Ok(FieldInfo {
access_flags,
name_index,
descriptor_index,
attributes,
})
}
fn read_method_info(cursor: &mut io::Cursor<&[u8]>) -> Result<MethodInfo, ParseError> {
let access_flags = cursor.read_u16::<BigEndian>()?;
let name_index = cursor.read_u16::<BigEndian>()?;
let descriptor_index = cursor.read_u16::<BigEndian>()?;
let attributes_count = cursor.read_u16::<BigEndian>()?;
let mut attributes = Vec::with_capacity(attributes_count as usize);
for _ in 0..attributes_count {
attributes.push(Self::read_attribute_info(cursor)?);
}
Ok(MethodInfo {
access_flags,
name_index,
descriptor_index,
attributes,
})
}
fn read_attribute_info(cursor: &mut io::Cursor<&[u8]>) -> Result<AttributeInfo, ParseError> {
let attribute_name_index = cursor.read_u16::<BigEndian>()?;
let length = cursor.read_u32::<BigEndian>()? as usize;
let mut info = vec![0u8; length];
cursor.read_exact(&mut info)?;
Ok(AttributeInfo {
attribute_name_index,
info,
})
}
pub fn get_string(&self, index: u16) -> Option<String> {
let entry = self.constant_pool.get(index as usize)?;
match entry {
ConstantPoolEntry::ConstantUtf8 { bytes } => String::from_utf8(bytes.clone()).ok(),
_ => None,
}
}
pub fn get_class_name(&self) -> Option<String> {
let class_entry = self.constant_pool.get(self.this_class as usize)?;
match class_entry {
ConstantPoolEntry::ConstantClass { name_index } => self.get_string(*name_index),
_ => None,
}
}
pub fn get_super_class_name(&self) -> Option<String> {
if self.super_class == 0 {
return None; }
let class_entry = self.constant_pool.get(self.super_class as usize)?;
match class_entry {
ConstantPoolEntry::ConstantClass { name_index } => self.get_string(*name_index),
_ => None,
}
}
pub fn get_class_name_from_index(&self, index: u16) -> Option<String> {
let entry = self.constant_pool.get(index as usize)?;
match entry {
ConstantPoolEntry::ConstantClass { name_index } => self.get_string(*name_index),
_ => None,
}
}
pub fn find_method(&self, name: &str, descriptor: &str) -> Option<&MethodInfo> {
self.methods.iter().find(|m| {
let method_name = self.get_string(m.name_index).unwrap_or_default();
let method_desc = self.get_string(m.descriptor_index).unwrap_or_default();
method_name == name && method_desc == descriptor
})
}
}