use std::io::Write;
use crate::{
metadata::method::{
encode_exception_handlers, ExceptionHandler, ExceptionHandlerFlags, MethodBodyFlags,
SectionFlags,
},
utils::{read_le, read_le_at},
Result,
};
const MAX_EXCEPTION_HANDLERS: usize = 1024;
fn validate_exception_handler_bounds(
handler: &ExceptionHandler,
code_size: u32,
handler_index: usize,
) -> Result<()> {
let try_end = handler
.try_offset
.checked_add(handler.try_length)
.ok_or_else(|| {
malformed_error!(
"Exception handler {} try region overflow: offset {} + length {} overflows u32",
handler_index,
handler.try_offset,
handler.try_length
)
})?;
if try_end > code_size {
return Err(malformed_error!(
"Exception handler {} try region [{}, {}) exceeds method code size {}",
handler_index,
handler.try_offset,
try_end,
code_size
));
}
let handler_end = handler
.handler_offset
.checked_add(handler.handler_length)
.ok_or_else(|| {
malformed_error!(
"Exception handler {} handler region overflow: offset {} + length {} overflows u32",
handler_index,
handler.handler_offset,
handler.handler_length
)
})?;
if handler_end > code_size {
return Err(malformed_error!(
"Exception handler {} handler region [{}, {}) exceeds method code size {}",
handler_index,
handler.handler_offset,
handler_end,
code_size
));
}
if handler.flags.contains(ExceptionHandlerFlags::FILTER) && handler.filter_offset >= code_size {
return Err(malformed_error!(
"Exception handler {} filter offset {} exceeds method code size {}",
handler_index,
handler.filter_offset,
code_size
));
}
Ok(())
}
pub struct MethodBody {
pub size_code: usize,
pub size_header: usize,
pub local_var_sig_token: u32,
pub max_stack: usize,
pub is_fat: bool,
pub is_init_local: bool,
pub is_exception_data: bool,
pub exception_handlers: Vec<ExceptionHandler>,
}
impl MethodBody {
pub fn from(data: &[u8]) -> Result<MethodBody> {
if data.is_empty() {
return Err(malformed_error!("Provided data for body parsing is empty"));
}
let first_byte = read_le::<u8>(data)?;
match MethodBodyFlags::from_bits_truncate(u16::from(first_byte & 0b_00000011_u8)) {
MethodBodyFlags::TINY_FORMAT => {
let size_code = (first_byte >> 2) as usize;
if size_code + 1 > data.len() {
return Err(out_of_bounds_error!());
}
Ok(MethodBody {
size_code,
size_header: 1,
local_var_sig_token: 0,
max_stack: 8,
is_fat: false,
is_init_local: false,
is_exception_data: false,
exception_handlers: Vec::new(),
})
}
MethodBodyFlags::FAT_FORMAT => {
if data.len() < 12 {
return Err(out_of_bounds_error!());
}
let first_duo = read_le::<u16>(data)?;
let size_header = (first_duo >> 12) * 4;
let size_code = read_le::<u32>(&data[4..])?;
if data.len() < (size_code as usize + size_header as usize) {
return Err(out_of_bounds_error!());
}
let local_var_sig_token = read_le::<u32>(&data[8..])?;
let flags_header =
MethodBodyFlags::from_bits_truncate(first_duo & 0b_0000111111111111_u16);
let max_stack = read_le::<u16>(&data[2..])? as usize;
let is_init_local = flags_header.contains(MethodBodyFlags::INIT_LOCALS);
let mut exception_handlers = Vec::new();
if flags_header.contains(MethodBodyFlags::MORE_SECTS) {
let mut cursor = size_header as usize + size_code as usize;
cursor = (cursor + 3) & !3;
while data.len() > (cursor + 4) {
let method_data_section_flags =
SectionFlags::from_bits_truncate(read_le::<u8>(&data[cursor..])?);
if !method_data_section_flags.contains(SectionFlags::EHTABLE) {
break;
}
if method_data_section_flags.contains(SectionFlags::FAT_FORMAT) {
let method_data_section_size =
read_le::<u32>(&data[cursor + 1..])? & 0x00FF_FFFF;
if method_data_section_size < 4
|| data.len() < (cursor + method_data_section_size as usize)
{
break;
}
let handler_count = (method_data_section_size - 4) / 24;
if handler_count as usize > MAX_EXCEPTION_HANDLERS {
return Err(malformed_error!(
"Method has too many exception handlers: {} (max: {})",
handler_count,
MAX_EXCEPTION_HANDLERS
));
}
cursor += 4;
for handler_idx in 0..handler_count {
let flags_u32 = read_le_at::<u32>(data, &mut cursor)?;
if flags_u32 > 0xFFFF {
return Err(malformed_error!(
"Exception handler {} has invalid flags with upper bits set: 0x{:08X}",
handler_idx,
flags_u32
));
}
#[allow(clippy::cast_possible_truncation)]
let flags =
ExceptionHandlerFlags::from_bits_truncate(flags_u32 as u16);
exception_handlers.push(ExceptionHandler {
flags,
try_offset: read_le_at::<u32>(data, &mut cursor)?,
try_length: read_le_at::<u32>(data, &mut cursor)?,
handler_offset: read_le_at::<u32>(data, &mut cursor)?,
handler_length: read_le_at::<u32>(data, &mut cursor)?,
filter_offset: read_le_at::<u32>(data, &mut cursor)?,
handler: None,
});
}
} else {
let method_data_section_size =
u32::from(read_le::<u8>(&data[cursor + 1..])?);
if method_data_section_size < 4
|| data.len() < (cursor + method_data_section_size as usize)
{
break;
}
let handler_count = (method_data_section_size - 4) / 12;
if handler_count as usize > MAX_EXCEPTION_HANDLERS {
return Err(malformed_error!(
"Method has too many exception handlers: {} (max: {})",
handler_count,
MAX_EXCEPTION_HANDLERS
));
}
cursor += 4;
for _ in 0..handler_count {
exception_handlers.push(ExceptionHandler {
flags: ExceptionHandlerFlags::from_bits_truncate(read_le_at::<
u16,
>(
data,
&mut cursor,
)?),
try_offset: u32::from(read_le_at::<u16>(data, &mut cursor)?),
try_length: u32::from(read_le_at::<u8>(data, &mut cursor)?),
handler_offset: u32::from(read_le_at::<u16>(
data,
&mut cursor,
)?),
handler_length: u32::from(read_le_at::<u8>(data, &mut cursor)?),
filter_offset: read_le_at::<u32>(data, &mut cursor)?,
handler: None,
});
}
}
if !method_data_section_flags.contains(SectionFlags::MORE_SECTS) {
break;
}
}
}
for (index, handler) in exception_handlers.iter().enumerate() {
validate_exception_handler_bounds(handler, size_code, index)?;
}
Ok(MethodBody {
size_code: size_code as usize,
size_header: size_header as usize,
local_var_sig_token,
max_stack,
is_fat: true,
is_init_local,
is_exception_data: !exception_handlers.is_empty(),
exception_handlers,
})
}
_ => Err(malformed_error!(
"MethodHeader is neither FAT nor TINY - {}",
first_byte
)),
}
}
#[must_use]
pub fn size(&self) -> usize {
let base_size = self.size_header + self.size_code;
if self.exception_handlers.is_empty() {
return base_size;
}
let aligned_base = (base_size + 3) & !3;
let needs_fat_format = self.exception_handlers.iter().any(|h| {
h.try_offset > 0xFFFF
|| h.try_length > 0xFF
|| h.handler_offset > 0xFFFF
|| h.handler_length > 0xFF
});
let section_size = if needs_fat_format {
4 + (self.exception_handlers.len() * 24)
} else {
4 + (self.exception_handlers.len() * 12)
};
aligned_base + section_size
}
pub fn write_to<W: Write>(&self, writer: &mut W, il_code: &[u8]) -> Result<u64> {
let code_size = il_code.len();
let has_exceptions = !self.exception_handlers.is_empty();
let use_tiny = code_size <= 63
&& self.max_stack <= 8
&& self.local_var_sig_token == 0
&& !has_exceptions;
let mut bytes_written: u64 = 0;
if use_tiny {
let code_size_u8 = u8::try_from(code_size).map_err(|_| {
malformed_error!("Code size {} exceeds tiny format limit", code_size)
})?;
let header_byte = (code_size_u8 << 2) | 0x02;
writer.write_all(&[header_byte])?;
bytes_written += 1;
} else {
let code_size_u32 = u32::try_from(code_size)
.map_err(|_| malformed_error!("Code size {} exceeds u32 range", code_size))?;
let max_stack_u16 = u16::try_from(self.max_stack)
.map_err(|_| malformed_error!("Max stack {} exceeds u16 range", self.max_stack))?;
let mut flags: u16 = 0x3003; if has_exceptions {
flags |= 0x0008; }
if self.is_init_local {
flags |= 0x0010; }
writer.write_all(&flags.to_le_bytes())?;
writer.write_all(&max_stack_u16.to_le_bytes())?;
writer.write_all(&code_size_u32.to_le_bytes())?;
writer.write_all(&self.local_var_sig_token.to_le_bytes())?;
bytes_written += 12;
}
writer.write_all(il_code)?;
bytes_written += code_size as u64;
if has_exceptions {
let padding = (4 - (bytes_written % 4)) % 4;
if padding > 0 {
writer.write_all(&vec![0u8; padding as usize])?;
bytes_written += padding;
}
let exception_bytes = encode_exception_handlers(&self.exception_handlers)?;
writer.write_all(&exception_bytes)?;
bytes_written += exception_bytes.len() as u64;
}
Ok(bytes_written)
}
}
#[cfg(test)]
mod tests {
use crate::metadata::method::ExceptionHandlerFlags;
use super::*;
#[test]
fn tiny() {
let data = include_bytes!("../../../tests/samples/WB_METHOD_TINY_0600032D.bin");
let method_header = MethodBody::from(data).unwrap();
assert!(!method_header.is_fat);
assert!(!method_header.is_exception_data);
assert!(!method_header.is_init_local);
assert_eq!(method_header.max_stack, 8);
assert_eq!(method_header.size_code, 18);
assert_eq!(method_header.size_header, 1);
assert_eq!(method_header.size(), 19);
assert_eq!(method_header.local_var_sig_token, 0);
}
#[test]
fn fat() {
let data = include_bytes!("../../../tests/samples/WB_METHOD_FAT_0600033E.bin");
let method_header = MethodBody::from(data).unwrap();
assert!(method_header.is_fat);
assert!(!method_header.is_exception_data);
assert!(method_header.is_init_local);
assert_eq!(method_header.max_stack, 5);
assert_eq!(method_header.size_code, 0x9B);
assert_eq!(method_header.size_header, 12);
assert_eq!(method_header.size(), 167);
assert_eq!(method_header.local_var_sig_token, 0x11000059);
}
#[test]
fn fat_exceptions_1() {
let data = include_bytes!("../../../tests/samples/WB_METHOD_FAT_EXCEPTION_06000341.bin");
let method_header = MethodBody::from(data).unwrap();
assert!(method_header.is_fat);
assert!(method_header.is_exception_data);
assert!(method_header.is_init_local);
assert_eq!(method_header.max_stack, 1);
assert_eq!(method_header.size_code, 30);
assert_eq!(method_header.size_header, 12);
assert_eq!(method_header.size(), 60); assert_eq!(method_header.local_var_sig_token, 0x11000003);
assert_eq!(method_header.exception_handlers.len(), 1);
assert!(method_header.exception_handlers[0]
.flags
.contains(ExceptionHandlerFlags::EXCEPTION));
assert_eq!(method_header.exception_handlers[0].try_offset, 0);
assert_eq!(method_header.exception_handlers[0].try_length, 0xF);
assert_eq!(method_header.exception_handlers[0].handler_offset, 0xF);
assert_eq!(method_header.exception_handlers[0].handler_length, 0xD);
assert_eq!(method_header.exception_handlers[0].filter_offset, 0x100003F);
}
#[test]
fn fat_exceptions_tiny_section_2() {
let data = include_bytes!(
"../../../tests/samples/WB_METHOD_FAT_EXCEPTION_N1_2LOCALS_060001AA.bin"
);
let method_header = MethodBody::from(data).unwrap();
assert!(method_header.is_fat);
assert!(method_header.is_exception_data);
assert!(method_header.is_init_local);
assert_eq!(method_header.max_stack, 3);
assert_eq!(method_header.size_code, 0x2E);
assert_eq!(method_header.size_header, 12);
assert_eq!(method_header.size(), 76); assert_eq!(method_header.local_var_sig_token, 0x1100001A);
assert_eq!(method_header.exception_handlers.len(), 1);
assert!(method_header.exception_handlers[0]
.flags
.contains(ExceptionHandlerFlags::FINALLY));
assert_eq!(method_header.exception_handlers[0].try_offset, 0x8);
assert_eq!(method_header.exception_handlers[0].try_length, 0x1B);
assert_eq!(method_header.exception_handlers[0].handler_offset, 0x23);
assert_eq!(method_header.exception_handlers[0].handler_length, 0xA);
assert_eq!(method_header.exception_handlers[0].filter_offset, 0);
}
#[test]
fn fat_exceptions_fat_section_3() {
let data = include_bytes!("../../../tests/samples/WB_METHOD_FAT_EXCEPTION_N2_06000421.bin");
let method_header = MethodBody::from(data).unwrap();
assert!(method_header.is_fat);
assert!(method_header.is_exception_data);
assert!(method_header.is_init_local);
assert_eq!(method_header.max_stack, 5);
assert_eq!(method_header.size_code, 0x19F);
assert_eq!(method_header.size_header, 12);
assert_eq!(method_header.size(), 480); assert_eq!(method_header.local_var_sig_token, 0x11000070);
assert_eq!(method_header.exception_handlers.len(), 2);
assert!(method_header.exception_handlers[0]
.flags
.contains(ExceptionHandlerFlags::FINALLY));
assert_eq!(method_header.exception_handlers[0].try_offset, 0x145);
assert_eq!(method_header.exception_handlers[0].try_length, 0x28);
assert_eq!(method_header.exception_handlers[0].handler_offset, 0x16D);
assert_eq!(method_header.exception_handlers[0].handler_length, 0xE);
assert_eq!(method_header.exception_handlers[0].filter_offset, 0);
assert!(method_header.exception_handlers[1]
.flags
.contains(ExceptionHandlerFlags::FINALLY));
assert_eq!(method_header.exception_handlers[1].try_offset, 0x9);
assert_eq!(method_header.exception_handlers[1].try_length, 0x18A);
assert_eq!(method_header.exception_handlers[1].handler_offset, 0x193);
assert_eq!(method_header.exception_handlers[1].handler_length, 0xA);
assert_eq!(method_header.exception_handlers[1].filter_offset, 0);
}
#[test]
fn fat_exceptions_multiple() {
let data = include_bytes!("../../../tests/samples/WB_METHOD_FAT_EXCEPTION_N2_06000D54.bin");
let method_header = MethodBody::from(data).unwrap();
assert!(method_header.is_fat);
assert!(method_header.is_exception_data);
assert!(method_header.is_init_local);
assert_eq!(method_header.max_stack, 3);
assert_eq!(method_header.size_code, 81);
assert_eq!(method_header.size_header, 12);
assert_eq!(method_header.size(), 124); assert_eq!(method_header.local_var_sig_token, 0x1100007C);
assert_eq!(method_header.exception_handlers.len(), 2);
assert!(method_header.exception_handlers[0]
.flags
.contains(ExceptionHandlerFlags::FINALLY));
assert_eq!(method_header.exception_handlers[0].try_offset, 17);
assert_eq!(method_header.exception_handlers[0].try_length, 48);
assert_eq!(method_header.exception_handlers[0].handler_offset, 65);
assert_eq!(method_header.exception_handlers[0].handler_length, 10);
assert_eq!(method_header.exception_handlers[0].filter_offset, 0);
assert!(method_header.exception_handlers[1]
.flags
.contains(ExceptionHandlerFlags::EXCEPTION));
assert_eq!(method_header.exception_handlers[1].try_offset, 0);
assert_eq!(method_header.exception_handlers[1].try_length, 77);
assert_eq!(method_header.exception_handlers[1].handler_offset, 77);
assert_eq!(method_header.exception_handlers[1].handler_length, 3);
assert_eq!(method_header.exception_handlers[1].filter_offset, 0x100001D);
}
}