#![allow(dead_code)]
use std::{
borrow::Cow,
fmt::{Debug, Formatter},
};
pub use crate::{
attribute::*,
cursor::{Cursor, ReadBytes},
error::{Error, Result},
field::*,
method::*,
utf8::Cesu8Bytes,
};
mod cursor;
const MAGIC: u32 = 0xCAFE_BABE;
const MIN_MAJOR_VERSION: u16 = 45;
const MAX_MAJOR_VERSION: u16 = 52;
pub type CpIndex = u16;
mod error {
use std::fmt::{Display, Formatter};
use cesu8::Cesu8DecodingError;
use crate::CpIndex;
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug)]
pub enum Error {
InvalidMagic {
magic: u32,
},
UnsupportedVersion {
major_version: u16,
minor_version: u16,
supported: std::ops::RangeInclusive<u16>,
},
InvalidConstantPoolCount {
count: u16,
},
InvalidConstantPoolTag {
tag: u8,
},
ConstantPoolOverflow {
index: CpIndex,
},
ConstantPoolIndexOutOfBounds {
index: CpIndex,
count: usize,
},
ConstantPoolIndexUnusable {
index: CpIndex,
},
UnexpectedConstantPoolEntry {
index: CpIndex,
expected: &'static str,
found: &'static str,
},
AttributeLengthMismatch {
name: String,
expected: usize,
consumed: usize,
},
InvalidReferenceKind {
kind: u8,
},
InvalidVerificationTypeTag {
tag: u8,
},
InvalidStackMapFrameTag {
tag: u8,
},
InvalidElementValueTag {
tag: u8,
},
InvalidTypeAnnotationTarget {
target_type: u8,
},
TrailingBytes {
count: usize,
},
InvalidCesu8(Cesu8DecodingError),
UnexpectedEof {
needed: usize,
available: usize,
},
}
impl Display for Error {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Self::InvalidMagic { magic } => write!(f, "invalid class magic 0x{magic:08X}"),
Self::UnsupportedVersion {
major_version,
minor_version,
supported,
} => write!(
f,
"unsupported class version {major_version}.{minor_version}, \
supported major versions are {}..={}",
supported.start(),
supported.end()
),
Self::InvalidConstantPoolCount { count } => {
write!(
f,
"invalid constant pool count {count}, expected at least 1"
)
}
Self::InvalidConstantPoolTag { tag } => {
write!(f, "invalid constant pool tag {tag}")
}
Self::ConstantPoolOverflow { index } => write!(
f,
"wide constant at index {index} overflows the constant pool"
),
Self::ConstantPoolIndexOutOfBounds { index, count } => write!(
f,
"constant pool index {index} is out of bounds (pool holds {count} slots)"
),
Self::ConstantPoolIndexUnusable { index } => write!(
f,
"constant pool index {index} points at the unusable slot of a wide constant"
),
Self::UnexpectedConstantPoolEntry {
index,
expected,
found,
} => write!(
f,
"constant pool index {index} refers to {found}, expected {expected}"
),
Self::AttributeLengthMismatch {
name,
expected,
consumed,
} => write!(
f,
"attribute {name} declares {expected} bytes but {consumed} were consumed"
),
Self::InvalidReferenceKind { kind } => {
write!(f, "invalid method handle reference kind {kind}")
}
Self::InvalidVerificationTypeTag { tag } => {
write!(f, "invalid verification type tag {tag}")
}
Self::InvalidStackMapFrameTag { tag } => {
write!(f, "invalid stack map frame tag {tag}")
}
Self::InvalidElementValueTag { tag } => {
write!(f, "invalid element value tag {tag} ({:?})", *tag as char)
}
Self::InvalidTypeAnnotationTarget { target_type } => {
write!(f, "invalid type annotation target type 0x{target_type:02X}")
}
Self::TrailingBytes { count } => {
write!(f, "{count} trailing bytes after the class file")
}
Self::InvalidCesu8(error) => write!(f, "invalid CESU-8 string: {error}"),
Self::UnexpectedEof { needed, available } => write!(
f,
"unexpected EOF: needed {needed} bytes, {available} available"
),
}
}
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::InvalidCesu8(error) => Some(error),
_ => None,
}
}
}
impl From<Cesu8DecodingError> for Error {
fn from(value: Cesu8DecodingError) -> Self {
Self::InvalidCesu8(value)
}
}
}
mod utf8 {
use std::{
borrow::Cow,
fmt::{Debug, Display, Formatter},
};
use crate::error::Result;
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct Cesu8Bytes<'a>(&'a [u8]);
impl<'a> Cesu8Bytes<'a> {
pub fn as_bytes(&self) -> &'a [u8] {
self.0
}
pub fn decode(&self) -> Result<Cow<'a, str>> {
Ok(cesu8::from_java_cesu8(self.0)?)
}
pub fn decode_owned(&self) -> Result<String> {
Ok(self.decode()?.into_owned())
}
}
impl Debug for Cesu8Bytes<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self.decode() {
Ok(string) => Debug::fmt(&string, f),
Err(_) => write!(f, "<invalid cesu-8: {:02X?}>", self.0),
}
}
}
impl Display for Cesu8Bytes<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self.decode() {
Ok(string) => f.write_str(&string),
Err(_) => write!(f, "<invalid cesu-8>"),
}
}
}
impl<'a> From<&'a [u8]> for Cesu8Bytes<'a> {
fn from(value: &'a [u8]) -> Self {
Self(value)
}
}
impl<'a> TryFrom<Cesu8Bytes<'a>> for String {
type Error = crate::error::Error;
fn try_from(value: Cesu8Bytes<'a>) -> Result<Self> {
value.decode_owned()
}
}
}
#[derive(Debug)]
pub struct Class<'a> {
pub minor_version: u16,
pub major_version: u16,
pub constant_pool: ConstantPool<'a>,
pub access_flags: ClassAccessFlags,
pub this_class: CpIndex,
pub super_class: Option<CpIndex>,
pub interfaces: Vec<CpIndex>,
pub fields: Vec<FieldInfo<'a>>,
pub methods: Vec<MethodInfo<'a>>,
pub attributes: Vec<AttributeInfo<'a>>,
}
impl<'a> Class<'a> {
pub fn parse(bytes: &'a [u8]) -> Result<Self> {
let mut cursor = Cursor::new(bytes);
let class = Self::try_read(&mut cursor)?;
if !cursor.is_empty() {
return Err(Error::TrailingBytes {
count: cursor.len(),
});
}
Ok(class)
}
pub fn try_read(cursor: &mut Cursor<'a>) -> Result<Self> {
let magic = cursor.read_u32()?;
if magic != MAGIC {
return Err(Error::InvalidMagic { magic });
}
let minor_version = cursor.read_u16()?;
let major_version = cursor.read_u16()?;
if !(MIN_MAJOR_VERSION..=MAX_MAJOR_VERSION).contains(&major_version) {
return Err(Error::UnsupportedVersion {
major_version,
minor_version,
supported: MIN_MAJOR_VERSION..=MAX_MAJOR_VERSION,
});
}
let constant_pool = ConstantPool::try_read(cursor)?;
let access_flags = ClassAccessFlags::from_bits_truncate(cursor.read_u16()?);
let this_class = cursor.read_u16()?;
constant_pool.class_name_index(this_class)?;
let super_class = cursor.read_optional_index()?;
if let Some(super_class) = super_class {
constant_pool.class_name_index(super_class)?;
}
let interfaces = cursor.read_indices()?;
for &interface in &interfaces {
constant_pool.class_name_index(interface)?;
}
let fields = cursor.repeat_by_u16(|cursor| FieldInfo::try_read(&constant_pool, cursor))?;
let methods =
cursor.repeat_by_u16(|cursor| MethodInfo::try_read(&constant_pool, cursor))?;
let attributes = AttributeInfo::try_read_all(&constant_pool, cursor)?;
Ok(Self {
minor_version,
major_version,
constant_pool,
access_flags,
this_class,
super_class,
interfaces,
fields,
methods,
attributes,
})
}
pub fn name(&self) -> Result<Cow<'a, str>> {
let name_index = self.constant_pool.class_name_index(self.this_class)?;
self.constant_pool.str(name_index)
}
pub fn signature(&self, constant_pool: &ConstantPool<'a>) -> Result<Option<Cow<'a, str>>> {
let signature_index = match self
.attributes
.iter()
.find(|attr| matches!(attr, AttributeInfo::Signature { .. }))
{
Some(AttributeInfo::Signature { signature_index }) => *signature_index,
Some(_) => unreachable!(),
None => return Ok(None),
};
Ok(Some(constant_pool.str(signature_index)?))
}
pub fn inner_classes(&self) -> Result<Option<&Vec<InnerClass>>> {
for attribute in &self.attributes {
match attribute {
AttributeInfo::InnerClasses { classes } => return Ok(Some(classes)),
_ => continue,
}
}
Ok(None)
}
}
pub struct ConstantPool<'a> {
slots: Vec<Option<ConstantPoolInfo<'a>>>,
}
impl<'a> ConstantPool<'a> {
fn try_read(cursor: &mut Cursor<'a>) -> Result<Self> {
let count = cursor.read_u16()?;
if count < 1 {
return Err(Error::InvalidConstantPoolCount { count });
}
let count = count as usize;
let mut slots = Vec::with_capacity(count);
slots.push(None);
while slots.len() < count {
let index = slots.len() as CpIndex;
let entry = ConstantPoolInfo::try_read(cursor)?;
let width = entry.slot_width();
slots.push(Some(entry));
if width == 2 {
if slots.len() >= count {
return Err(Error::ConstantPoolOverflow { index });
}
slots.push(None);
}
}
Ok(Self { slots })
}
pub fn len(&self) -> usize {
self.slots.len()
}
pub fn is_empty(&self) -> bool {
self.slots.len() <= 1
}
pub fn iter(&self) -> impl Iterator<Item = (CpIndex, &ConstantPoolInfo<'a>)> {
self.slots
.iter()
.enumerate()
.filter_map(|(index, entry)| Some((index as CpIndex, entry.as_ref()?)))
}
pub fn get(&self, index: CpIndex) -> Result<&ConstantPoolInfo<'a>> {
match self.slots.get(index as usize) {
Some(Some(entry)) => Ok(entry),
Some(None) if index != 0 => Err(Error::ConstantPoolIndexUnusable { index }),
_ => Err(Error::ConstantPoolIndexOutOfBounds {
index,
count: self.slots.len(),
}),
}
}
pub fn class_name_index(&self, index: CpIndex) -> Result<CpIndex> {
match self.get(index)? {
ConstantPoolInfo::Class { name_index } => Ok(*name_index),
found => Err(unexpected_entry(index, "Class", found)),
}
}
pub fn utf8(&self, index: CpIndex) -> Result<Cesu8Bytes<'a>> {
match self.get(index)? {
ConstantPoolInfo::Utf8 { bytes } => Ok(*bytes),
found => Err(unexpected_entry(index, "Utf8", found)),
}
}
pub fn str(&self, index: CpIndex) -> Result<Cow<'a, str>> {
self.utf8(index)?.decode()
}
pub fn name_and_type(&self, index: CpIndex) -> Result<(CpIndex, CpIndex)> {
match self.get(index)? {
ConstantPoolInfo::NameAndType {
name_index,
descriptor_index,
} => Ok((*name_index, *descriptor_index)),
found => Err(unexpected_entry(index, "NameAndType", found)),
}
}
pub fn expect_constant_value(&self, index: CpIndex) -> Result<()> {
match self.get(index)? {
ConstantPoolInfo::Integer { .. }
| ConstantPoolInfo::Float { .. }
| ConstantPoolInfo::Long { .. }
| ConstantPoolInfo::Double { .. }
| ConstantPoolInfo::String { .. } => Ok(()),
found => Err(unexpected_entry(
index,
"Integer, Float, Long, Double or String",
found,
)),
}
}
}
fn unexpected_entry(index: CpIndex, expected: &'static str, found: &ConstantPoolInfo<'_>) -> Error {
Error::UnexpectedConstantPoolEntry {
index,
expected,
found: found.kind(),
}
}
impl Debug for ConstantPool<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_map().entries(self.iter()).finish()
}
}
#[derive(Debug)]
pub enum ConstantPoolInfo<'a> {
Utf8 {
bytes: Cesu8Bytes<'a>,
},
Integer {
value: i32,
},
Float {
value: f32,
},
Long {
value: i64,
},
Double {
value: f64,
},
Class {
name_index: CpIndex,
},
String {
string_index: CpIndex,
},
FieldRef {
class_index: CpIndex,
name_and_type_index: CpIndex,
},
MethodRef {
class_index: CpIndex,
name_and_type_index: CpIndex,
},
InterfaceMethodRef {
class_index: CpIndex,
name_and_type_index: CpIndex,
},
NameAndType {
name_index: CpIndex,
descriptor_index: CpIndex,
},
MethodHandle {
reference_kind: ReferenceKind,
reference_index: CpIndex,
},
MethodType {
descriptor_index: CpIndex,
},
InvokeDynamic {
bootstrap_method_attr_index: u16,
name_and_type_index: CpIndex,
},
}
impl<'a> ConstantPoolInfo<'a> {
fn try_read(cursor: &mut Cursor<'a>) -> Result<Self> {
let info = match ConstantPoolTag::try_read(cursor)? {
ConstantPoolTag::Utf8 => {
let length = cursor.read_u16()? as usize;
Self::Utf8 {
bytes: Cesu8Bytes::from(cursor.read_bytes(length)?),
}
}
ConstantPoolTag::Integer => Self::Integer {
value: cursor.read_i32()?,
},
ConstantPoolTag::Float => Self::Float {
value: cursor.read_f32()?,
},
ConstantPoolTag::Long => Self::Long {
value: cursor.read_i64()?,
},
ConstantPoolTag::Double => Self::Double {
value: cursor.read_f64()?,
},
ConstantPoolTag::Class => Self::Class {
name_index: cursor.read_u16()?,
},
ConstantPoolTag::String => Self::String {
string_index: cursor.read_u16()?,
},
ConstantPoolTag::FieldRef => Self::FieldRef {
class_index: cursor.read_u16()?,
name_and_type_index: cursor.read_u16()?,
},
ConstantPoolTag::MethodRef => Self::MethodRef {
class_index: cursor.read_u16()?,
name_and_type_index: cursor.read_u16()?,
},
ConstantPoolTag::InterfaceMethodRef => Self::InterfaceMethodRef {
class_index: cursor.read_u16()?,
name_and_type_index: cursor.read_u16()?,
},
ConstantPoolTag::NameAndType => Self::NameAndType {
name_index: cursor.read_u16()?,
descriptor_index: cursor.read_u16()?,
},
ConstantPoolTag::MethodHandle => Self::MethodHandle {
reference_kind: ReferenceKind::try_read(cursor)?,
reference_index: cursor.read_u16()?,
},
ConstantPoolTag::MethodType => Self::MethodType {
descriptor_index: cursor.read_u16()?,
},
ConstantPoolTag::InvokeDynamic => Self::InvokeDynamic {
bootstrap_method_attr_index: cursor.read_u16()?,
name_and_type_index: cursor.read_u16()?,
},
};
Ok(info)
}
pub fn slot_width(&self) -> u16 {
match self {
Self::Long { .. } | Self::Double { .. } => 2,
_ => 1,
}
}
pub fn kind(&self) -> &'static str {
match self {
Self::Utf8 { .. } => "Utf8",
Self::Integer { .. } => "Integer",
Self::Float { .. } => "Float",
Self::Long { .. } => "Long",
Self::Double { .. } => "Double",
Self::Class { .. } => "Class",
Self::String { .. } => "String",
Self::FieldRef { .. } => "FieldRef",
Self::MethodRef { .. } => "MethodRef",
Self::InterfaceMethodRef { .. } => "InterfaceMethodRef",
Self::NameAndType { .. } => "NameAndType",
Self::MethodHandle { .. } => "MethodHandle",
Self::MethodType { .. } => "MethodType",
Self::InvokeDynamic { .. } => "InvokeDynamic",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum ConstantPoolTag {
Utf8 = 1,
Integer = 3,
Float = 4,
Long = 5,
Double = 6,
Class = 7,
String = 8,
FieldRef = 9,
MethodRef = 10,
InterfaceMethodRef = 11,
NameAndType = 12,
MethodHandle = 15,
MethodType = 16,
InvokeDynamic = 18,
}
impl TryFrom<u8> for ConstantPoolTag {
type Error = Error;
fn try_from(value: u8) -> Result<Self> {
let tag = match value {
1 => Self::Utf8,
3 => Self::Integer,
4 => Self::Float,
5 => Self::Long,
6 => Self::Double,
7 => Self::Class,
8 => Self::String,
9 => Self::FieldRef,
10 => Self::MethodRef,
11 => Self::InterfaceMethodRef,
12 => Self::NameAndType,
15 => Self::MethodHandle,
16 => Self::MethodType,
18 => Self::InvokeDynamic,
tag => return Err(Error::InvalidConstantPoolTag { tag }),
};
Ok(tag)
}
}
impl ConstantPoolTag {
pub fn try_read(cursor: &mut Cursor<'_>) -> Result<Self> {
Self::try_from(cursor.read_u8()?)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum ReferenceKind {
GetField = 1,
GetStatic = 2,
PutField = 3,
PutStatic = 4,
InvokeVirtual = 5,
InvokeStatic = 6,
InvokeSpecial = 7,
NewInvokeSpecial = 8,
InvokeInterface = 9,
}
impl TryFrom<u8> for ReferenceKind {
type Error = Error;
fn try_from(value: u8) -> Result<Self> {
let kind = match value {
1 => Self::GetField,
2 => Self::GetStatic,
3 => Self::PutField,
4 => Self::PutStatic,
5 => Self::InvokeVirtual,
6 => Self::InvokeStatic,
7 => Self::InvokeSpecial,
8 => Self::NewInvokeSpecial,
9 => Self::InvokeInterface,
kind => return Err(Error::InvalidReferenceKind { kind }),
};
Ok(kind)
}
}
impl ReferenceKind {
pub fn try_read(cursor: &mut Cursor<'_>) -> Result<Self> {
Self::try_from(cursor.read_u8()?)
}
}
bitflags::bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ClassAccessFlags: u16 {
const PUBLIC = 0x0001;
const FINAL = 0x0010;
const SUPER = 0x0020;
const INTERFACE = 0x0200;
const ABSTRACT = 0x0400;
const SYNTHETIC = 0x1000;
const ANNOTATION = 0x2000;
const ENUM = 0x4000;
}
}
mod field {
use std::borrow::Cow;
use crate::{
AttributeInfo, ConstantPool, CpIndex,
cursor::{Cursor, ReadBytes},
error::Result,
};
#[derive(Debug)]
pub struct FieldInfo<'a> {
pub access_flags: FieldAccessFlags,
pub name_index: CpIndex,
pub descriptor_index: CpIndex,
pub attributes: Vec<AttributeInfo<'a>>,
}
impl<'a> FieldInfo<'a> {
pub fn try_read(constant_pool: &ConstantPool<'a>, cursor: &mut Cursor<'a>) -> Result<Self> {
let access_flags = FieldAccessFlags::from_bits_truncate(cursor.read_u16()?);
let name_index = cursor.read_u16()?;
constant_pool.utf8(name_index)?;
let descriptor_index = cursor.read_u16()?;
constant_pool.utf8(descriptor_index)?;
Ok(Self {
access_flags,
name_index,
descriptor_index,
attributes: AttributeInfo::try_read_all(constant_pool, cursor)?,
})
}
pub fn signature(&self, constant_pool: &ConstantPool<'a>) -> Result<Option<Cow<'a, str>>> {
let signature_index = match self
.attributes
.iter()
.find(|attr| matches!(attr, AttributeInfo::Signature { .. }))
{
Some(AttributeInfo::Signature { signature_index }) => *signature_index,
Some(_) => unreachable!(),
None => return Ok(None),
};
Ok(Some(constant_pool.str(signature_index)?))
}
pub fn name(&self, constant_pool: &ConstantPool<'a>) -> Result<Cow<'a, str>> {
constant_pool.str(self.name_index)
}
pub fn descriptor(&self, constant_pool: &ConstantPool<'a>) -> Result<Cow<'a, str>> {
constant_pool.str(self.descriptor_index)
}
}
bitflags::bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct FieldAccessFlags: u16 {
const PUBLIC = 0x0001;
const PRIVATE = 0x0002;
const PROTECTED = 0x0004;
const STATIC = 0x0008;
const FINAL = 0x0010;
const VOLATILE = 0x0040;
const TRANSIENT = 0x0080;
const SYNTHETIC = 0x1000;
const ENUM = 0x4000;
}
}
}
mod method {
use std::borrow::Cow;
use crate::{
AttributeInfo, ConstantPool, CpIndex,
cursor::{Cursor, ReadBytes},
error::Result,
};
#[derive(Debug)]
pub struct MethodInfo<'a> {
pub access_flags: MethodAccessFlags,
pub name_index: CpIndex,
pub descriptor_index: CpIndex,
pub attributes: Vec<AttributeInfo<'a>>,
}
impl<'a> MethodInfo<'a> {
pub fn try_read(constant_pool: &ConstantPool<'a>, cursor: &mut Cursor<'a>) -> Result<Self> {
let access_flags = MethodAccessFlags::from_bits_truncate(cursor.read_u16()?);
let name_index = cursor.read_u16()?;
constant_pool.utf8(name_index)?;
let descriptor_index = cursor.read_u16()?;
constant_pool.utf8(descriptor_index)?;
Ok(Self {
access_flags,
name_index,
descriptor_index,
attributes: AttributeInfo::try_read_all(constant_pool, cursor)?,
})
}
pub fn signature(&self, constant_pool: &ConstantPool<'a>) -> Result<Option<Cow<'a, str>>> {
let signature_index = match self
.attributes
.iter()
.find(|attr| matches!(attr, AttributeInfo::Signature { .. }))
{
Some(AttributeInfo::Signature { signature_index }) => *signature_index,
Some(_) => unreachable!(),
None => return Ok(None),
};
Ok(Some(constant_pool.str(signature_index)?))
}
pub fn name(&self, constant_pool: &ConstantPool<'a>) -> Result<Cow<'a, str>> {
constant_pool.str(self.name_index)
}
pub fn descriptor(&self, constant_pool: &ConstantPool<'a>) -> Result<Cow<'a, str>> {
constant_pool.str(self.descriptor_index)
}
}
bitflags::bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MethodAccessFlags: u16 {
const PUBLIC = 0x0001;
const PRIVATE = 0x0002;
const PROTECTED = 0x0004;
const STATIC = 0x0008;
const FINAL = 0x0010;
const SYNCHRONIZED = 0x0020;
const BRIDGE = 0x0040;
const VARARGS = 0x0080;
const NATIVE = 0x0100;
const ABSTRACT = 0x0400;
const STRICT = 0x0800;
const SYNTHETIC = 0x1000;
}
}
}
mod attribute {
use crate::{
ConstantPool, CpIndex,
cursor::{Cursor, ReadBytes},
error::{Error, Result},
};
#[derive(Debug)]
pub enum AttributeInfo<'a> {
SourceFile {
sourcefile_index: CpIndex,
},
InnerClasses {
classes: Vec<InnerClass>,
},
EnclosingMethod {
class_index: CpIndex,
method_index: Option<CpIndex>,
},
SourceDebugExtension {
debug_extension: &'a [u8],
},
BootstrapMethods {
bootstrap_methods: Vec<BootstrapMethod>,
},
ConstantValue {
constantvalue_index: CpIndex,
},
Code {
max_stack: u16,
max_locals: u16,
code: &'a [u8],
exception_table: Vec<ExceptionHandler>,
attributes: Vec<AttributeInfo<'a>>,
},
Exceptions {
exception_index_table: Vec<CpIndex>,
},
RuntimeVisibleParameterAnnotations {
parameter_annotations: Vec<Vec<Annotation>>,
},
RuntimeInvisibleParameterAnnotations {
parameter_annotations: Vec<Vec<Annotation>>,
},
AnnotationDefault {
default_value: ElementValue,
},
MethodParameters {
parameters: Vec<MethodParameter>,
},
Synthetic,
Deprecated,
Signature {
signature_index: CpIndex,
},
RuntimeVisibleAnnotations {
annotations: Vec<Annotation>,
},
RuntimeInvisibleAnnotations {
annotations: Vec<Annotation>,
},
LineNumberTable {
line_number_table: Vec<LineNumber>,
},
LocalVariableTable {
local_variable_table: Vec<LocalVariable>,
},
LocalVariableTypeTable {
local_variable_type_table: Vec<LocalVariableType>,
},
StackMapTable {
entries: Vec<StackMapFrame>,
},
RuntimeVisibleTypeAnnotations {
annotations: Vec<TypeAnnotation>,
},
RuntimeInvisibleTypeAnnotations {
annotations: Vec<TypeAnnotation>,
},
Unknown {
name: String,
info: &'a [u8],
},
}
impl<'a> AttributeInfo<'a> {
pub fn try_read_all(
constant_pool: &ConstantPool<'a>,
cursor: &mut Cursor<'a>,
) -> Result<Vec<Self>> {
cursor.repeat_by_u16(|cursor| Self::try_read(constant_pool, cursor))
}
pub fn try_read(constant_pool: &ConstantPool<'a>, cursor: &mut Cursor<'a>) -> Result<Self> {
let attribute_name_index = cursor.read_u16()?;
let name = constant_pool.utf8(attribute_name_index)?;
let attribute_length = cursor.read_u32()? as usize;
let mut info = Cursor::new(cursor.read_bytes(attribute_length)?);
let attribute = Self::try_read_body(constant_pool, name.as_bytes(), &mut info)?;
if !info.is_empty() {
return Err(Error::AttributeLengthMismatch {
name: name.decode_owned()?,
expected: attribute_length,
consumed: attribute_length - info.len(),
});
}
Ok(attribute)
}
fn try_read_body(
constant_pool: &ConstantPool<'a>,
name: &[u8],
cursor: &mut Cursor<'a>,
) -> Result<Self> {
let attribute = match name {
b"SourceFile" => {
let sourcefile_index = cursor.read_u16()?;
constant_pool.utf8(sourcefile_index)?;
Self::SourceFile { sourcefile_index }
}
b"InnerClasses" => Self::InnerClasses {
classes: cursor
.repeat_by_u16(|cursor| InnerClass::try_read(constant_pool, cursor))?,
},
b"EnclosingMethod" => {
let class_index = cursor.read_u16()?;
constant_pool.class_name_index(class_index)?;
let method_index = cursor.read_optional_index()?;
if let Some(method_index) = method_index {
constant_pool.name_and_type(method_index)?;
}
Self::EnclosingMethod {
class_index,
method_index,
}
}
b"SourceDebugExtension" => Self::SourceDebugExtension {
debug_extension: cursor.read_rest(),
},
b"BootstrapMethods" => Self::BootstrapMethods {
bootstrap_methods: cursor.repeat_by_u16(BootstrapMethod::try_read)?,
},
b"ConstantValue" => {
let constantvalue_index = cursor.read_u16()?;
constant_pool.expect_constant_value(constantvalue_index)?;
Self::ConstantValue {
constantvalue_index,
}
}
b"Code" => {
let max_stack = cursor.read_u16()?;
let max_locals = cursor.read_u16()?;
let code_length = cursor.read_u32()? as usize;
let code = cursor.read_bytes(code_length)?;
let exception_table = cursor.repeat_by_u16(|cursor| {
ExceptionHandler::try_read(constant_pool, cursor)
})?;
Self::Code {
max_stack,
max_locals,
code,
exception_table,
attributes: Self::try_read_all(constant_pool, cursor)?,
}
}
b"Exceptions" => {
let exception_index_table = cursor.read_indices()?;
for &index in &exception_index_table {
constant_pool.class_name_index(index)?;
}
Self::Exceptions {
exception_index_table,
}
}
b"RuntimeVisibleParameterAnnotations" => Self::RuntimeVisibleParameterAnnotations {
parameter_annotations: cursor
.repeat_by_u8(|cursor| cursor.repeat_by_u16(Annotation::try_read))?,
},
b"RuntimeInvisibleParameterAnnotations" => {
Self::RuntimeInvisibleParameterAnnotations {
parameter_annotations: cursor
.repeat_by_u8(|cursor| cursor.repeat_by_u16(Annotation::try_read))?,
}
}
b"AnnotationDefault" => Self::AnnotationDefault {
default_value: ElementValue::try_read(cursor)?,
},
b"MethodParameters" => Self::MethodParameters {
parameters: cursor
.repeat_by_u8(|cursor| MethodParameter::try_read(constant_pool, cursor))?,
},
b"Synthetic" => Self::Synthetic,
b"Deprecated" => Self::Deprecated,
b"Signature" => {
let signature_index = cursor.read_u16()?;
constant_pool.utf8(signature_index)?;
Self::Signature { signature_index }
}
b"RuntimeVisibleAnnotations" => Self::RuntimeVisibleAnnotations {
annotations: cursor.repeat_by_u16(Annotation::try_read)?,
},
b"RuntimeInvisibleAnnotations" => Self::RuntimeInvisibleAnnotations {
annotations: cursor.repeat_by_u16(Annotation::try_read)?,
},
b"LineNumberTable" => Self::LineNumberTable {
line_number_table: cursor.repeat_by_u16(LineNumber::try_read)?,
},
b"LocalVariableTable" => Self::LocalVariableTable {
local_variable_table: cursor
.repeat_by_u16(|cursor| LocalVariable::try_read(constant_pool, cursor))?,
},
b"LocalVariableTypeTable" => Self::LocalVariableTypeTable {
local_variable_type_table: cursor.repeat_by_u16(|cursor| {
LocalVariableType::try_read(constant_pool, cursor)
})?,
},
b"StackMapTable" => Self::StackMapTable {
entries: cursor.repeat_by_u16(StackMapFrame::try_read)?,
},
b"RuntimeVisibleTypeAnnotations" => Self::RuntimeVisibleTypeAnnotations {
annotations: cursor.repeat_by_u16(TypeAnnotation::try_read)?,
},
b"RuntimeInvisibleTypeAnnotations" => Self::RuntimeInvisibleTypeAnnotations {
annotations: cursor.repeat_by_u16(TypeAnnotation::try_read)?,
},
_ => Self::Unknown {
name: String::from_utf8_lossy(name).into_owned(),
info: cursor.read_rest(),
},
};
Ok(attribute)
}
}
#[derive(Debug)]
pub struct ExceptionHandler {
pub start_pc: u16,
pub end_pc: u16,
pub handler_pc: u16,
pub catch_type: Option<CpIndex>,
}
impl ExceptionHandler {
pub fn try_read(constant_pool: &ConstantPool<'_>, cursor: &mut Cursor<'_>) -> Result<Self> {
let start_pc = cursor.read_u16()?;
let end_pc = cursor.read_u16()?;
let handler_pc = cursor.read_u16()?;
let catch_type = cursor.read_optional_index()?;
if let Some(catch_type) = catch_type {
constant_pool.class_name_index(catch_type)?;
}
Ok(Self {
start_pc,
end_pc,
handler_pc,
catch_type,
})
}
}
#[derive(Debug)]
pub enum StackMapFrame {
Same {
offset_delta: u16,
},
SameLocals1StackItem {
offset_delta: u16,
stack: VerificationTypeInfo,
},
SameLocals1StackItemExtended {
offset_delta: u16,
stack: VerificationTypeInfo,
},
Chop {
offset_delta: u16,
absent_locals: u8,
},
SameExtended {
offset_delta: u16,
},
Append {
offset_delta: u16,
locals: Vec<VerificationTypeInfo>,
},
Full {
offset_delta: u16,
locals: Vec<VerificationTypeInfo>,
stack: Vec<VerificationTypeInfo>,
},
}
impl StackMapFrame {
pub fn try_read(cursor: &mut Cursor<'_>) -> Result<Self> {
let tag = cursor.read_u8()?;
let frame = match tag {
0..=63 => Self::Same {
offset_delta: tag as u16,
},
64..=127 => Self::SameLocals1StackItem {
offset_delta: (tag - 64) as u16,
stack: VerificationTypeInfo::try_read(cursor)?,
},
247 => Self::SameLocals1StackItemExtended {
offset_delta: cursor.read_u16()?,
stack: VerificationTypeInfo::try_read(cursor)?,
},
248..=250 => Self::Chop {
offset_delta: cursor.read_u16()?,
absent_locals: 251 - tag,
},
251 => Self::SameExtended {
offset_delta: cursor.read_u16()?,
},
252..=254 => {
let offset_delta = cursor.read_u16()?;
let locals = (0..tag - 251)
.map(|_| VerificationTypeInfo::try_read(cursor))
.collect::<Result<Vec<_>>>()?;
Self::Append {
offset_delta,
locals,
}
}
255 => Self::Full {
offset_delta: cursor.read_u16()?,
locals: cursor.repeat_by_u16(VerificationTypeInfo::try_read)?,
stack: cursor.repeat_by_u16(VerificationTypeInfo::try_read)?,
},
tag => return Err(Error::InvalidStackMapFrameTag { tag }),
};
Ok(frame)
}
}
#[derive(Debug)]
pub enum VerificationTypeInfo {
Top,
Integer,
Float,
Double,
Long,
Null,
UninitializedThis,
Object { cpool_index: CpIndex },
Uninitialized { offset: u16 },
}
impl VerificationTypeInfo {
pub fn try_read(cursor: &mut Cursor<'_>) -> Result<Self> {
let info = match VerificationTypeTag::try_read(cursor)? {
VerificationTypeTag::Top => Self::Top,
VerificationTypeTag::Integer => Self::Integer,
VerificationTypeTag::Float => Self::Float,
VerificationTypeTag::Double => Self::Double,
VerificationTypeTag::Long => Self::Long,
VerificationTypeTag::Null => Self::Null,
VerificationTypeTag::UninitializedThis => Self::UninitializedThis,
VerificationTypeTag::Object => Self::Object {
cpool_index: cursor.read_u16()?,
},
VerificationTypeTag::Uninitialized => Self::Uninitialized {
offset: cursor.read_u16()?,
},
};
Ok(info)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum VerificationTypeTag {
Top = 0,
Integer = 1,
Float = 2,
Double = 3,
Long = 4,
Null = 5,
UninitializedThis = 6,
Object = 7,
Uninitialized = 8,
}
impl TryFrom<u8> for VerificationTypeTag {
type Error = Error;
fn try_from(value: u8) -> Result<Self> {
let tag = match value {
0 => Self::Top,
1 => Self::Integer,
2 => Self::Float,
3 => Self::Double,
4 => Self::Long,
5 => Self::Null,
6 => Self::UninitializedThis,
7 => Self::Object,
8 => Self::Uninitialized,
tag => return Err(Error::InvalidVerificationTypeTag { tag }),
};
Ok(tag)
}
}
impl VerificationTypeTag {
pub fn try_read(cursor: &mut Cursor<'_>) -> Result<Self> {
Self::try_from(cursor.read_u8()?)
}
}
#[derive(Debug)]
pub struct InnerClass {
pub inner_class_info_index: CpIndex,
pub outer_class_info_index: Option<CpIndex>,
pub inner_name_index: Option<CpIndex>,
pub inner_class_access_flags: InnerClassAccessFlags,
}
impl InnerClass {
pub fn try_read(constant_pool: &ConstantPool<'_>, cursor: &mut Cursor<'_>) -> Result<Self> {
let inner_class_info_index = cursor.read_u16()?;
constant_pool.class_name_index(inner_class_info_index)?;
let outer_class_info_index = cursor.read_optional_index()?;
if let Some(index) = outer_class_info_index {
constant_pool.class_name_index(index)?;
}
let inner_name_index = cursor.read_optional_index()?;
if let Some(index) = inner_name_index {
constant_pool.utf8(index)?;
}
Ok(Self {
inner_class_info_index,
outer_class_info_index,
inner_name_index,
inner_class_access_flags: InnerClassAccessFlags::from_bits_truncate(
cursor.read_u16()?,
),
})
}
}
bitflags::bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct InnerClassAccessFlags: u16 {
const PUBLIC = 0x0001;
const PRIVATE = 0x0002;
const PROTECTED = 0x0004;
const STATIC = 0x0008;
const FINAL = 0x0010;
const INTERFACE = 0x0200;
const ABSTRACT = 0x0400;
const SYNTHETIC = 0x1000;
const ANNOTATION = 0x2000;
const ENUM = 0x4000;
}
}
#[derive(Debug)]
pub struct LineNumber {
pub start_pc: u16,
pub line_number: u16,
}
impl LineNumber {
pub fn try_read(cursor: &mut Cursor<'_>) -> Result<Self> {
Ok(Self {
start_pc: cursor.read_u16()?,
line_number: cursor.read_u16()?,
})
}
}
#[derive(Debug)]
pub struct LocalVariable {
pub start_pc: u16,
pub length: u16,
pub name_index: CpIndex,
pub descriptor_index: CpIndex,
pub index: u16,
}
impl LocalVariable {
pub fn try_read(constant_pool: &ConstantPool<'_>, cursor: &mut Cursor<'_>) -> Result<Self> {
let start_pc = cursor.read_u16()?;
let length = cursor.read_u16()?;
let name_index = cursor.read_u16()?;
constant_pool.utf8(name_index)?;
let descriptor_index = cursor.read_u16()?;
constant_pool.utf8(descriptor_index)?;
Ok(Self {
start_pc,
length,
name_index,
descriptor_index,
index: cursor.read_u16()?,
})
}
}
#[derive(Debug)]
pub struct LocalVariableType {
pub start_pc: u16,
pub length: u16,
pub name_index: CpIndex,
pub signature_index: CpIndex,
pub index: u16,
}
impl LocalVariableType {
pub fn try_read(constant_pool: &ConstantPool<'_>, cursor: &mut Cursor<'_>) -> Result<Self> {
let start_pc = cursor.read_u16()?;
let length = cursor.read_u16()?;
let name_index = cursor.read_u16()?;
constant_pool.utf8(name_index)?;
let signature_index = cursor.read_u16()?;
constant_pool.utf8(signature_index)?;
Ok(Self {
start_pc,
length,
name_index,
signature_index,
index: cursor.read_u16()?,
})
}
}
#[derive(Debug)]
pub struct Annotation {
pub type_index: CpIndex,
pub element_value_pairs: Vec<ElementValuePair>,
}
impl Annotation {
pub fn try_read(cursor: &mut Cursor<'_>) -> Result<Self> {
Ok(Self {
type_index: cursor.read_u16()?,
element_value_pairs: cursor.repeat_by_u16(ElementValuePair::try_read)?,
})
}
}
#[derive(Debug)]
pub struct ElementValuePair {
pub element_name_index: CpIndex,
pub value: ElementValue,
}
impl ElementValuePair {
pub fn try_read(cursor: &mut Cursor<'_>) -> Result<Self> {
Ok(Self {
element_name_index: cursor.read_u16()?,
value: ElementValue::try_read(cursor)?,
})
}
}
#[derive(Debug)]
pub enum ElementValue {
Const {
tag: u8,
const_value_index: CpIndex,
},
Enum {
type_name_index: CpIndex,
const_name_index: CpIndex,
},
Class {
class_info_index: CpIndex,
},
Annotation {
annotation: Annotation,
},
Array {
values: Vec<ElementValue>,
},
}
impl ElementValue {
pub fn try_read(cursor: &mut Cursor<'_>) -> Result<Self> {
let tag = cursor.read_u8()?;
let value = match tag {
b'B' | b'C' | b'D' | b'F' | b'I' | b'J' | b'S' | b'Z' | b's' => Self::Const {
tag,
const_value_index: cursor.read_u16()?,
},
b'e' => Self::Enum {
type_name_index: cursor.read_u16()?,
const_name_index: cursor.read_u16()?,
},
b'c' => Self::Class {
class_info_index: cursor.read_u16()?,
},
b'@' => Self::Annotation {
annotation: Annotation::try_read(cursor)?,
},
b'[' => Self::Array {
values: cursor.repeat_by_u16(Self::try_read)?,
},
tag => return Err(Error::InvalidElementValueTag { tag }),
};
Ok(value)
}
}
#[derive(Debug)]
pub struct TypeAnnotation {
pub target: TypeAnnotationTarget,
pub target_path: TypePath,
pub type_index: CpIndex,
pub element_value_pairs: Vec<ElementValuePair>,
}
impl TypeAnnotation {
pub fn try_read(cursor: &mut Cursor<'_>) -> Result<Self> {
Ok(Self {
target: TypeAnnotationTarget::try_read(cursor)?,
target_path: TypePath::try_read(cursor)?,
type_index: cursor.read_u16()?,
element_value_pairs: cursor.repeat_by_u16(ElementValuePair::try_read)?,
})
}
}
#[derive(Debug)]
pub enum TypeAnnotationTarget {
TypeParameter {
type_parameter_index: u8,
},
SuperType {
supertype_index: u16,
},
TypeParameterBound {
type_parameter_index: u8,
bound_index: u8,
},
Empty,
FormalParameter {
formal_parameter_index: u8,
},
Throws {
throws_type_index: u16,
},
LocalVar {
table: Vec<LocalVarTarget>,
},
Catch {
exception_table_index: u16,
},
Offset {
offset: u16,
},
TypeArgument {
offset: u16,
type_argument_index: u8,
},
}
impl TypeAnnotationTarget {
pub fn try_read(cursor: &mut Cursor<'_>) -> Result<Self> {
let target = match cursor.read_u8()? {
0x00 | 0x01 => Self::TypeParameter {
type_parameter_index: cursor.read_u8()?,
},
0x10 => Self::SuperType {
supertype_index: cursor.read_u16()?,
},
0x11 | 0x12 => Self::TypeParameterBound {
type_parameter_index: cursor.read_u8()?,
bound_index: cursor.read_u8()?,
},
0x13..=0x15 => Self::Empty,
0x16 => Self::FormalParameter {
formal_parameter_index: cursor.read_u8()?,
},
0x17 => Self::Throws {
throws_type_index: cursor.read_u16()?,
},
0x40 | 0x41 => Self::LocalVar {
table: cursor.repeat_by_u16(LocalVarTarget::try_read)?,
},
0x42 => Self::Catch {
exception_table_index: cursor.read_u16()?,
},
0x43..=0x46 => Self::Offset {
offset: cursor.read_u16()?,
},
0x47..=0x4B => Self::TypeArgument {
offset: cursor.read_u16()?,
type_argument_index: cursor.read_u8()?,
},
target_type => return Err(Error::InvalidTypeAnnotationTarget { target_type }),
};
Ok(target)
}
}
#[derive(Debug)]
pub struct LocalVarTarget {
pub start_pc: u16,
pub length: u16,
pub index: u16,
}
impl LocalVarTarget {
pub fn try_read(cursor: &mut Cursor<'_>) -> Result<Self> {
Ok(Self {
start_pc: cursor.read_u16()?,
length: cursor.read_u16()?,
index: cursor.read_u16()?,
})
}
}
#[derive(Debug)]
pub struct TypePath {
pub path: Vec<TypePathEntry>,
}
impl TypePath {
pub fn try_read(cursor: &mut Cursor<'_>) -> Result<Self> {
Ok(Self {
path: cursor.repeat_by_u8(TypePathEntry::try_read)?,
})
}
}
#[derive(Debug)]
pub struct TypePathEntry {
pub type_path_kind: u8,
pub type_argument_index: u8,
}
impl TypePathEntry {
pub fn try_read(cursor: &mut Cursor<'_>) -> Result<Self> {
Ok(Self {
type_path_kind: cursor.read_u8()?,
type_argument_index: cursor.read_u8()?,
})
}
}
#[derive(Debug)]
pub struct BootstrapMethod {
pub bootstrap_method_ref: CpIndex,
pub bootstrap_arguments: Vec<CpIndex>,
}
impl BootstrapMethod {
pub fn try_read(cursor: &mut Cursor<'_>) -> Result<Self> {
Ok(Self {
bootstrap_method_ref: cursor.read_u16()?,
bootstrap_arguments: cursor.read_indices()?,
})
}
}
#[derive(Debug)]
pub struct MethodParameter {
pub name_index: Option<CpIndex>,
pub access_flags: MethodParameterAccessFlags,
}
impl MethodParameter {
pub fn try_read(constant_pool: &ConstantPool<'_>, cursor: &mut Cursor<'_>) -> Result<Self> {
let name_index = cursor.read_optional_index()?;
if let Some(name_index) = name_index {
constant_pool.utf8(name_index)?;
}
Ok(Self {
name_index,
access_flags: MethodParameterAccessFlags::from_bits_truncate(cursor.read_u16()?),
})
}
}
bitflags::bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MethodParameterAccessFlags: u16 {
const FINAL = 0x0010;
const SYNTHETIC = 0x1000;
const MANDATED = 0x8000;
}
}
}