#[derive(Clone, Debug, Eq, PartialEq)]
pub struct NestedAttribute {
pub name_index: u16,
pub owner: NestedAttributeOwner,
pub order: usize,
pub declared_length: u32,
pub bytes: Vec<u8>,
pub origin: AttributeOrigin,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum NestedAttributeOwner {
Code,
RecordComponent,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct CodeException {
pub start_pc: u16,
pub end_pc: u16,
pub handler_pc: u16,
pub catch_type: u16,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct CodeAttribute {
pub max_stack: u16,
pub max_locals: u16,
pub code: Vec<u8>,
pub exception_table: Vec<CodeException>,
pub attributes: Vec<NestedAttribute>,
}
impl CodeAttribute {
pub fn decode(reader: &mut ByteReader<'_>) -> Result<Self, AttributeError> {
let max_stack = reader.read_u2()?;
let max_locals = reader.read_u2()?;
let code_len = usize::try_from(reader.read_u4()?).map_err(|_| {
error(
AttributeErrorKind::CountOverflow,
reader.offset(),
"code length is not addressable",
)
})?;
reader.preflight_allocation(code_len)?;
let code = reader.take(code_len)?.to_vec();
let exception_count = usize::from(reader.read_u2()?);
reader.preflight_allocation(exception_count)?;
let mut exception_table = Vec::with_capacity(exception_count);
for _ in 0..exception_count {
exception_table.push(CodeException {
start_pc: reader.read_u2()?,
end_pc: reader.read_u2()?,
handler_pc: reader.read_u2()?,
catch_type: reader.read_u2()?,
});
}
validate_code_shape(code.len(), &exception_table)?;
let attribute_count = usize::from(reader.read_u2()?);
reader.preflight_allocation(attribute_count)?;
let mut attributes = Vec::with_capacity(attribute_count);
for order in 0..attribute_count {
let start = reader.offset();
let name_index = reader.read_u2()?;
let declared_length = reader.read_u4()?;
let length = usize::try_from(declared_length).map_err(|_| {
error(
AttributeErrorKind::CountOverflow,
reader.offset(),
"nested attribute length is not addressable",
)
})?;
reader.preflight_allocation(length)?;
attributes.push(NestedAttribute {
name_index,
owner: NestedAttributeOwner::Code,
order,
declared_length,
bytes: reader.take(length)?.to_vec(),
origin: annotation_origin(start, reader),
});
}
finish(reader)?;
Ok(Self {
max_stack,
max_locals,
code,
exception_table,
attributes,
})
}
pub fn encode(&self, budget: usize) -> Result<Vec<u8>, AttributeError> {
validate_code_shape(self.code.len(), &self.exception_table)?;
let mut out = ByteWriter::new(budget);
out.write_u2(self.max_stack)?;
out.write_u2(self.max_locals)?;
out.write_u4(
u32::try_from(self.code.len())
.map_err(|_| error(AttributeErrorKind::CountOverflow, 0, "code is too long"))?,
)?;
out.write_bytes(&self.code)?;
out.write_u2(count(self.exception_table.len(), "exception handlers")?)?;
for row in &self.exception_table {
out.write_u2(row.start_pc)?;
out.write_u2(row.end_pc)?;
out.write_u2(row.handler_pc)?;
out.write_u2(row.catch_type)?;
}
out.write_u2(count(self.attributes.len(), "nested attributes")?)?;
for attribute in &self.attributes {
if usize::try_from(attribute.declared_length).ok() != Some(attribute.bytes.len()) {
return Err(error(
AttributeErrorKind::StaticConstraint,
attribute.origin.start,
"nested attribute declared length differs from retained bytes",
));
}
out.write_u2(attribute.name_index)?;
out.write_u4(attribute.declared_length)?;
out.write_bytes(&attribute.bytes)?;
}
Ok(out.into_bytes())
}
}
fn validate_code_shape(
code_length: usize,
exceptions: &[CodeException],
) -> Result<(), AttributeError> {
if !(1..=u16::MAX as usize).contains(&code_length) {
return Err(error(
AttributeErrorKind::StaticConstraint,
0,
format!("Code array length {code_length} is outside 1..=65535"),
));
}
for exception in exceptions {
let start = usize::from(exception.start_pc);
let end = usize::from(exception.end_pc);
let handler = usize::from(exception.handler_pc);
if start >= end || end > code_length || handler >= code_length {
return Err(error(
AttributeErrorKind::StaticConstraint,
start,
format!(
"exception range {start}..{end} with handler {handler} is outside Code length {code_length}"
),
));
}
}
Ok(())
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum VerificationType {
Top,
Integer,
Float,
Double,
Long,
Null,
UninitializedThis,
Object(u16),
Uninitialized(u16),
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum StackMapFrame {
Same {
frame_type: u8,
},
SameLocalsOneStack {
frame_type: u8,
stack: VerificationType,
},
SameLocalsOneStackExtended {
offset_delta: u16,
stack: VerificationType,
},
Chop {
frame_type: u8,
offset_delta: u16,
},
SameExtended {
offset_delta: u16,
},
Append {
frame_type: u8,
offset_delta: u16,
locals: Vec<VerificationType>,
},
Full {
offset_delta: u16,
locals: Vec<VerificationType>,
stack: Vec<VerificationType>,
},
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct StackMapTableAttribute {
pub frames: Vec<StackMapFrame>,
}
impl StackMapTableAttribute {
pub fn decode(reader: &mut ByteReader<'_>) -> Result<Self, AttributeError> {
let n = usize::from(reader.read_u2()?);
reader.preflight_allocation(n)?;
let mut frames = Vec::with_capacity(n);
for _ in 0..n {
frames.push(decode_frame(reader)?);
}
finish(reader)?;
Ok(Self { frames })
}
pub fn encode(&self, budget: usize) -> Result<Vec<u8>, AttributeError> {
let mut out = ByteWriter::new(budget);
out.write_u2(count(self.frames.len(), "stack-map frames")?)?;
for frame in &self.frames {
encode_frame(frame, &mut out)?;
}
Ok(out.into_bytes())
}
}
fn decode_type(reader: &mut ByteReader<'_>) -> Result<VerificationType, AttributeError> {
let at = reader.offset();
Ok(match reader.read_u1()? {
0 => VerificationType::Top,
1 => VerificationType::Integer,
2 => VerificationType::Float,
3 => VerificationType::Double,
4 => VerificationType::Long,
5 => VerificationType::Null,
6 => VerificationType::UninitializedThis,
7 => VerificationType::Object(reader.read_u2()?),
8 => VerificationType::Uninitialized(reader.read_u2()?),
tag => {
return Err(error(
AttributeErrorKind::ReservedTag,
at,
format!("reserved verification type tag {tag}"),
));
}
})
}
fn encode_type(value: VerificationType, out: &mut ByteWriter) -> Result<(), AttributeError> {
let (tag, extra) = match value {
VerificationType::Top => (0, None),
VerificationType::Integer => (1, None),
VerificationType::Float => (2, None),
VerificationType::Double => (3, None),
VerificationType::Long => (4, None),
VerificationType::Null => (5, None),
VerificationType::UninitializedThis => (6, None),
VerificationType::Object(v) => (7, Some(v)),
VerificationType::Uninitialized(v) => (8, Some(v)),
};
out.write_u1(tag)?;
if let Some(v) = extra {
out.write_u2(v)?;
}
Ok(())
}
fn decode_frame(r: &mut ByteReader<'_>) -> Result<StackMapFrame, AttributeError> {
let at = r.offset();
let tag = r.read_u1()?;
Ok(match tag {
0..=63 => StackMapFrame::Same { frame_type: tag },
64..=127 => StackMapFrame::SameLocalsOneStack {
frame_type: tag,
stack: decode_type(r)?,
},
128..=246 => {
return Err(error(
AttributeErrorKind::ReservedTag,
at,
format!("reserved stack-map frame tag {tag}"),
));
}
247 => StackMapFrame::SameLocalsOneStackExtended {
offset_delta: r.read_u2()?,
stack: decode_type(r)?,
},
248..=250 => StackMapFrame::Chop {
frame_type: tag,
offset_delta: r.read_u2()?,
},
251 => StackMapFrame::SameExtended {
offset_delta: r.read_u2()?,
},
252..=254 => {
let offset_delta = r.read_u2()?;
let mut locals = Vec::with_capacity(usize::from(tag - 251));
for _ in 0..tag - 251 {
locals.push(decode_type(r)?);
}
StackMapFrame::Append {
frame_type: tag,
offset_delta,
locals,
}
}
255 => {
let offset_delta = r.read_u2()?;
let nl = usize::from(r.read_u2()?);
r.preflight_allocation(nl)?;
let mut locals = Vec::with_capacity(nl);
for _ in 0..nl {
locals.push(decode_type(r)?);
}
let ns = usize::from(r.read_u2()?);
r.preflight_allocation(ns)?;
let mut stack = Vec::with_capacity(ns);
for _ in 0..ns {
stack.push(decode_type(r)?);
}
StackMapFrame::Full {
offset_delta,
locals,
stack,
}
}
})
}
fn encode_frame(f: &StackMapFrame, out: &mut ByteWriter) -> Result<(), AttributeError> {
match f {
StackMapFrame::Same { frame_type: t } if *t <= 63 => out.write_u1(*t)?,
StackMapFrame::SameLocalsOneStack {
frame_type: t,
stack,
} if (64..=127).contains(t) => {
out.write_u1(*t)?;
encode_type(*stack, out)?
}
StackMapFrame::SameLocalsOneStackExtended {
offset_delta,
stack,
} => {
out.write_u1(247)?;
out.write_u2(*offset_delta)?;
encode_type(*stack, out)?
}
StackMapFrame::Chop {
frame_type: t,
offset_delta,
} if (248..=250).contains(t) => {
out.write_u1(*t)?;
out.write_u2(*offset_delta)?
}
StackMapFrame::SameExtended { offset_delta } => {
out.write_u1(251)?;
out.write_u2(*offset_delta)?
}
StackMapFrame::Append {
frame_type: t,
offset_delta,
locals,
} if (252..=254).contains(t) && locals.len() == usize::from(*t - 251) => {
out.write_u1(*t)?;
out.write_u2(*offset_delta)?;
for v in locals {
encode_type(*v, out)?
}
}
StackMapFrame::Full {
offset_delta,
locals,
stack,
} => {
out.write_u1(255)?;
out.write_u2(*offset_delta)?;
out.write_u2(count(locals.len(), "full-frame locals")?)?;
for v in locals {
encode_type(*v, out)?
}
out.write_u2(count(stack.len(), "full-frame stack entries")?)?;
for v in stack {
encode_type(*v, out)?
}
}
_ => {
return Err(error(
AttributeErrorKind::ReservedTag,
0,
"frame variant contains a tag or arity outside its static format",
));
}
}
Ok(())
}