use crate::Result;
pub fn encode_method_body_header(
code_size: u32,
max_stack: u16,
local_var_sig_tok: u32,
has_exceptions: bool,
init_locals: bool,
) -> Result<Vec<u8>> {
if code_size <= 63 && max_stack <= 8 && local_var_sig_tok == 0 && !has_exceptions {
let header = u8::try_from((code_size << 2) | 0x02)
.map_err(|_| malformed_error!("Method body header value exceeds u8 range"))?;
Ok(vec![header])
} else {
let mut header = Vec::with_capacity(12);
let mut flags: u16 = 0x3003; if has_exceptions {
flags |= 0x0008; }
if init_locals {
flags |= 0x0010; }
header.extend_from_slice(&flags.to_le_bytes());
header.extend_from_slice(&max_stack.to_le_bytes());
header.extend_from_slice(&code_size.to_le_bytes());
header.extend_from_slice(&local_var_sig_tok.to_le_bytes());
Ok(header)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::metadata::method::body::MethodBody;
#[test]
fn test_tiny_format_encoding() -> Result<()> {
let header = encode_method_body_header(2, 1, 0, false, false)?;
assert_eq!(header.len(), 1);
let dummy_code = vec![0x17, 0x2A]; let mut method_body = header;
method_body.extend_from_slice(&dummy_code);
let parsed = MethodBody::from(&method_body)?;
assert_eq!(parsed.size_code, 2);
assert_eq!(parsed.size_header, 1);
assert_eq!(parsed.max_stack, 8); assert_eq!(parsed.local_var_sig_token, 0);
assert!(!parsed.is_fat);
assert!(!parsed.is_exception_data);
Ok(())
}
#[test]
fn test_fat_format_encoding() -> Result<()> {
let header = encode_method_body_header(100, 16, 0x11000001, false, true)?;
assert_eq!(header.len(), 12);
let dummy_code = vec![0x00; 100]; let mut method_body = header;
method_body.extend_from_slice(&dummy_code);
let parsed = MethodBody::from(&method_body)?;
assert_eq!(parsed.size_code, 100);
assert_eq!(parsed.size_header, 12);
assert_eq!(parsed.max_stack, 16);
assert_eq!(parsed.local_var_sig_token, 0x11000001);
assert!(parsed.is_fat);
assert!(!parsed.is_exception_data); assert!(parsed.is_init_local);
Ok(())
}
#[test]
fn test_fat_format_without_exceptions() -> Result<()> {
let header = encode_method_body_header(70, 12, 0x11000002, false, true)?;
let dummy_code = vec![0x00; 70]; let mut method_body = header;
method_body.extend_from_slice(&dummy_code);
let parsed = MethodBody::from(&method_body)?;
assert_eq!(parsed.size_code, 70);
assert_eq!(parsed.size_header, 12);
assert_eq!(parsed.max_stack, 12);
assert_eq!(parsed.local_var_sig_token, 0x11000002);
assert!(parsed.is_fat);
assert!(!parsed.is_exception_data);
Ok(())
}
#[test]
fn test_format_selection() -> Result<()> {
assert_eq!(encode_method_body_header(63, 8, 0, false, false)?.len(), 1);
assert_eq!(encode_method_body_header(64, 8, 0, false, true)?.len(), 12); assert_eq!(encode_method_body_header(63, 9, 0, false, true)?.len(), 12); assert_eq!(encode_method_body_header(63, 8, 1, false, true)?.len(), 12); assert_eq!(encode_method_body_header(63, 8, 0, true, true)?.len(), 12);
Ok(())
}
#[test]
fn test_fat_format_exception_flag() -> Result<()> {
let header_with_exceptions = encode_method_body_header(100, 5, 0, true, true)?;
let header_without_exceptions = encode_method_body_header(100, 5, 0, false, true)?;
assert_eq!(header_with_exceptions.len(), 12);
assert_eq!(header_without_exceptions.len(), 12);
let flags_with = u16::from_le_bytes([header_with_exceptions[0], header_with_exceptions[1]]);
let flags_without =
u16::from_le_bytes([header_without_exceptions[0], header_without_exceptions[1]]);
assert_eq!(flags_with & 0x0008, 0x0008); assert_eq!(flags_without & 0x0008, 0x0000);
Ok(())
}
#[test]
fn test_fat_format_init_locals_flag() -> Result<()> {
let header_with_init = encode_method_body_header(100, 5, 0, false, true)?;
let header_without_init = encode_method_body_header(100, 5, 0, false, false)?;
assert_eq!(header_with_init.len(), 12);
assert_eq!(header_without_init.len(), 12);
let flags_with = u16::from_le_bytes([header_with_init[0], header_with_init[1]]);
let flags_without = u16::from_le_bytes([header_without_init[0], header_without_init[1]]);
assert_eq!(flags_with & 0x0010, 0x0010); assert_eq!(flags_without & 0x0010, 0x0000);
Ok(())
}
#[test]
fn test_tiny_format_boundary_conditions() -> Result<()> {
let header = encode_method_body_header(63, 8, 0, false, false)?;
let dummy_code = vec![0x00; 63]; let mut method_body = header;
method_body.extend_from_slice(&dummy_code);
let parsed = MethodBody::from(&method_body)?;
assert_eq!(parsed.size_code, 63);
assert!(!parsed.is_fat);
assert_eq!(parsed.max_stack, 8);
Ok(())
}
#[test]
fn test_real_method_simulation() -> Result<()> {
let code = vec![0x17, 0x2A]; let header = encode_method_body_header(code.len() as u32, 1, 0, false, false)?;
let mut method_body = header;
method_body.extend_from_slice(&code);
let parsed = MethodBody::from(&method_body)?;
assert_eq!(parsed.size_code, 2);
assert!(!parsed.is_fat);
assert_eq!(parsed.max_stack, 8); assert_eq!(parsed.local_var_sig_token, 0);
assert!(!parsed.is_exception_data);
Ok(())
}
}