#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct ShellBudget {
pub interfaces: usize,
pub fields: usize,
pub methods: usize,
pub attributes: usize,
pub attribute_bytes: usize,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct AttributeShell {
pub name_index: u16,
pub declared_length: u32,
pub bytes: Vec<u8>,
pub origin: Origin,
pub location: AttributeLocation,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum AttributeOwner {
Class,
Field(usize),
Method(usize),
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct AttributeLocation {
pub owner: AttributeOwner,
pub order: usize,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct LayoutInvalidation {
pub path: String,
pub shifts_following_layout: bool,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct EditReport {
pub invalidated: Vec<LayoutInvalidation>,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct FieldShell {
pub access_flags: u16,
pub name_index: u16,
pub descriptor_index: u16,
pub attributes: Vec<AttributeShell>,
pub origin: Origin,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct MethodShell {
pub access_flags: u16,
pub name_index: u16,
pub descriptor_index: u16,
pub attributes: Vec<AttributeShell>,
pub origin: Origin,
}
#[derive(Clone, Debug, PartialEq)]
pub struct ClassShell {
pub minor_version: u16,
pub major_version: u16,
pub constant_pool: ConstantPool,
pub access_flags: u16,
pub this_class: u16,
pub super_class: u16,
pub interfaces: Vec<u16>,
pub fields: Vec<FieldShell>,
pub methods: Vec<MethodShell>,
pub attributes: Vec<AttributeShell>,
pub origin: Origin,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct ClassIndex(pub u16);
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Utf8Index(pub u16);
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ValidatedFieldShell {
pub name: Utf8Index,
pub descriptor: Utf8Index,
pub attribute_names: Vec<Utf8Index>,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ValidatedMethodShell {
pub name: Utf8Index,
pub descriptor: Utf8Index,
pub attribute_names: Vec<Utf8Index>,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ValidatedClassShell {
pub this_class: ClassIndex,
pub super_class: Option<ClassIndex>,
pub interfaces: Vec<ClassIndex>,
pub fields: Vec<ValidatedFieldShell>,
pub methods: Vec<ValidatedMethodShell>,
pub attribute_names: Vec<Utf8Index>,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ShellErrorKind {
Magic,
Bytes,
ConstantPool,
Budget,
InvalidIndex,
TrailingBytes,
Edit,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ShellError {
pub kind: ShellErrorKind,
pub offset: usize,
pub index: Option<u16>,
pub path: String,
pub message: String,
}
impl fmt::Display for ShellError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{} at {} (byte {})",
self.message, self.path, self.offset
)
}
}
impl std::error::Error for ShellError {}
impl ClassShell {
pub fn decode(
bytes: &[u8],
allocation_budget: usize,
budget: ShellBudget,
codec: CodecId,
source: SourceId,
) -> Result<Self, ShellError> {
let mut reader = ByteReader::new(bytes, allocation_budget);
if reader.read_u4().map_err(|e| byte_error("magic", e))? != 0xcafe_babe {
return Err(error(
ShellErrorKind::Magic,
0,
None,
"magic",
"invalid classfile magic",
));
}
let minor_version = reader
.read_u2()
.map_err(|e| byte_error("minor_version", e))?;
let major_version = reader
.read_u2()
.map_err(|e| byte_error("major_version", e))?;
let constant_pool = ConstantPool::decode(&mut reader, major_version).map_err(pool_error)?;
let access_flags = reader
.read_u2()
.map_err(|e| byte_error("access_flags", e))?;
let this_class = reader.read_u2().map_err(|e| byte_error("this_class", e))?;
let super_class = reader.read_u2().map_err(|e| byte_error("super_class", e))?;
let mut state = DecodeState {
budget,
attributes: 0,
attribute_bytes: 0,
codec,
source,
};
let interfaces = read_indices(&mut reader, budget.interfaces, "interfaces")?;
let fields = read_members(
&mut reader,
budget.fields,
"fields",
AttributeOwner::Field,
&mut state,
)?
.into_iter()
.map(Member::into_field)
.collect();
let methods = read_members(
&mut reader,
budget.methods,
"methods",
AttributeOwner::Method,
&mut state,
)?
.into_iter()
.map(Member::into_method)
.collect();
let attributes =
read_attributes(&mut reader, "attributes", AttributeOwner::Class, &mut state)?;
if reader.remaining() != 0 {
return Err(error(
ShellErrorKind::TrailingBytes,
reader.offset(),
None,
"class",
"trailing bytes after class shell",
));
}
Ok(Self {
minor_version,
major_version,
constant_pool,
access_flags,
this_class,
super_class,
interfaces,
fields,
methods,
attributes,
origin: origin(codec, state.source, 0, reader.offset()),
})
}
pub fn validate(&self) -> Result<ValidatedClassShell, ShellError> {
let this_class = self.class_index(self.this_class, "this_class", &self.origin)?;
let super_class = if self.super_class == 0 {
None
} else {
Some(self.class_index(self.super_class, "super_class", &self.origin)?)
};
let interfaces = self
.interfaces
.iter()
.enumerate()
.map(|(position, &index)| {
self.class_index(index, &format!("interfaces[{position}]"), &self.origin)
})
.collect::<Result<_, _>>()?;
let fields = self
.fields
.iter()
.enumerate()
.map(|(position, member)| {
Ok(ValidatedFieldShell {
name: self.utf8_index(
member.name_index,
&format!("fields[{position}].name_index"),
&member.origin,
)?,
descriptor: self.utf8_index(
member.descriptor_index,
&format!("fields[{position}].descriptor_index"),
&member.origin,
)?,
attribute_names: self
.validate_attributes(&member.attributes, &format!("fields[{position}]"))?,
})
})
.collect::<Result<_, ShellError>>()?;
let methods = self
.methods
.iter()
.enumerate()
.map(|(position, member)| {
Ok(ValidatedMethodShell {
name: self.utf8_index(
member.name_index,
&format!("methods[{position}].name_index"),
&member.origin,
)?,
descriptor: self.utf8_index(
member.descriptor_index,
&format!("methods[{position}].descriptor_index"),
&member.origin,
)?,
attribute_names: self
.validate_attributes(&member.attributes, &format!("methods[{position}]"))?,
})
})
.collect::<Result<_, ShellError>>()?;
Ok(ValidatedClassShell {
this_class,
super_class,
interfaces,
fields,
methods,
attribute_names: self.validate_attributes(&self.attributes, "class")?,
})
}
pub fn encode(&self, allocation_budget: usize) -> Result<Vec<u8>, ShellError> {
self.validate()?;
let mut out = ByteWriter::new(allocation_budget);
out.write_u4(0xcafe_babe)
.map_err(|e| byte_error("magic", e))?;
out.write_u2(self.minor_version)
.map_err(|e| byte_error("minor_version", e))?;
out.write_u2(self.major_version)
.map_err(|e| byte_error("major_version", e))?;
self.constant_pool
.encode(&mut out, self.major_version)
.map_err(pool_error)?;
out.write_u2(self.access_flags)
.map_err(|e| byte_error("access_flags", e))?;
out.write_u2(self.this_class)
.map_err(|e| byte_error("this_class", e))?;
out.write_u2(self.super_class)
.map_err(|e| byte_error("super_class", e))?;
write_indices(&mut out, &self.interfaces, "interfaces")?;
write_members(&mut out, &self.fields, "fields")?;
write_members(&mut out, &self.methods, "methods")?;
write_attributes(&mut out, &self.attributes, "class")?;
Ok(out.into_bytes())
}
pub fn replace_method_code(
&mut self,
method_index: usize,
code: Vec<u8>,
allocation_budget: usize,
) -> Result<EditReport, ShellError> {
let code_name = self.constant_pool.slots().iter().position(|slot| {
matches!(slot, crate::ConstantSlot::Entry(Constant::Utf8(value)) if value.as_code_units() == ['C' as u16, 'o' as u16, 'd' as u16, 'e' as u16])
}).ok_or_else(|| edit_error("constant_pool", "constant pool does not contain Code"))? as u16;
let method = self.methods.get_mut(method_index).ok_or_else(|| {
edit_error(
format!("methods[{method_index}]"),
"method index is out of range",
)
})?;
let (attribute_index, attribute) = method
.attributes
.iter_mut()
.enumerate()
.find(|(_, attribute)| attribute.name_index == code_name)
.ok_or_else(|| {
edit_error(
format!("methods[{method_index}]"),
"method has no Code attribute",
)
})?;
let old_len = attribute.bytes.len();
let mut structured =
CodeAttribute::decode(&mut ByteReader::new(&attribute.bytes, allocation_budget))
.map_err(|cause| {
edit_error(
format!("methods[{method_index}].attributes[{attribute_index}]"),
cause.to_string(),
)
})?;
structured.code = code;
let bytes = structured.encode(allocation_budget).map_err(|cause| {
edit_error(
format!("methods[{method_index}].attributes[{attribute_index}]"),
cause.to_string(),
)
})?;
attribute.declared_length = u32::try_from(bytes.len())
.map_err(|_| edit_error("Code", "encoded Code attribute exceeds u32"))?;
attribute.bytes = bytes;
Ok(EditReport {
invalidated: vec![LayoutInvalidation {
path: format!("methods[{method_index}].attributes[{attribute_index}].bytes"),
shifts_following_layout: old_len != attribute.bytes.len(),
}],
})
}
fn class_index(&self, index: u16, path: &str, at: &Origin) -> Result<ClassIndex, ShellError> {
self.expect(index, path, at, |entry| {
matches!(entry, Constant::Class { .. })
})?;
Ok(ClassIndex(index))
}
fn utf8_index(&self, index: u16, path: &str, at: &Origin) -> Result<Utf8Index, ShellError> {
self.expect(index, path, at, |entry| matches!(entry, Constant::Utf8(_)))?;
Ok(Utf8Index(index))
}
fn expect(
&self,
index: u16,
path: &str,
at: &Origin,
predicate: impl FnOnce(&Constant) -> bool,
) -> Result<(), ShellError> {
let entry = self.constant_pool.entry(index, index).map_err(|cause| {
error(
ShellErrorKind::InvalidIndex,
at.span.start,
Some(index),
path,
format!("invalid constant-pool index {index}: {cause}"),
)
})?;
if !predicate(entry) {
return Err(error(
ShellErrorKind::InvalidIndex,
at.span.start,
Some(index),
path,
format!("constant-pool index {index} has the wrong category"),
));
}
Ok(())
}
fn validate_attributes(
&self,
attributes: &[AttributeShell],
owner: &str,
) -> Result<Vec<Utf8Index>, ShellError> {
attributes
.iter()
.enumerate()
.map(|(position, attribute)| {
self.utf8_index(
attribute.name_index,
&format!("{owner}.attributes[{position}].name_index"),
&attribute.origin,
)
})
.collect()
}
}