use crate::attributes::Instruction;
use crate::{BaseType, ConstantPool, FieldType, Method, MethodAccessFlags, Result};
pub trait MaxLocals {
fn max_locals(&self, constant_pool: &ConstantPool<'_>, method: &Method) -> Result<u16>;
}
impl MaxLocals for [Instruction] {
#[expect(clippy::bool_to_int_with_if)]
fn max_locals(&self, constant_pool: &ConstantPool<'_>, method: &Method) -> Result<u16> {
let mut max_locals: u16 = if method.access_flags.contains(MethodAccessFlags::STATIC) {
0
} else {
1
};
let method_descriptor = constant_pool.try_get_utf8(method.descriptor_index)?;
let (parameters, _return_type) = FieldType::parse_method_descriptor(method_descriptor)?;
for parameter in parameters {
match parameter {
FieldType::Base(BaseType::Double | BaseType::Long) => {
max_locals = max_locals.saturating_add(2);
}
_ => {
max_locals = max_locals.saturating_add(1);
}
}
}
for instruction in self {
if let Some(local_index) = instruction.max_locals_index()? {
let max_local_index = local_index.saturating_add(1);
max_locals = max_locals.max(max_local_index);
}
}
Ok(max_locals)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::attributes::Instruction;
fn get_max_locals(
access_flags: MethodAccessFlags,
descriptor: &str,
instructions: &[Instruction],
) -> Result<u16> {
let mut constant_pool = ConstantPool::new();
let descriptor_index = constant_pool.add_utf8(descriptor)?;
let method = Method {
access_flags,
descriptor_index,
..Default::default()
};
instructions.max_locals(&constant_pool, &method)
}
#[test]
fn test_static() -> Result<()> {
let max_locals = get_max_locals(MethodAccessFlags::STATIC, "()V", &[])?;
assert_eq!(0, max_locals);
Ok(())
}
#[test]
fn test_static_with_int_parameter() -> Result<()> {
let max_locals = get_max_locals(MethodAccessFlags::STATIC, "(I)V", &[])?;
assert_eq!(1, max_locals);
Ok(())
}
#[test]
fn test_static_with_double_parameter() -> Result<()> {
let max_locals = get_max_locals(MethodAccessFlags::STATIC, "(D)V", &[])?;
assert_eq!(2, max_locals);
Ok(())
}
#[test]
fn test_static_with_long_parameter() -> Result<()> {
let max_locals = get_max_locals(MethodAccessFlags::STATIC, "(J)V", &[])?;
assert_eq!(2, max_locals);
Ok(())
}
#[test]
fn test_virtual() -> Result<()> {
let max_locals = get_max_locals(MethodAccessFlags::empty(), "()V", &[])?;
assert_eq!(1, max_locals);
Ok(())
}
#[test]
fn test_virtual_with_int_parameter() -> Result<()> {
let max_locals = get_max_locals(MethodAccessFlags::empty(), "(I)V", &[])?;
assert_eq!(2, max_locals);
Ok(())
}
#[test]
fn test_virtual_with_double_parameter() -> Result<()> {
let max_locals = get_max_locals(MethodAccessFlags::empty(), "(D)V", &[])?;
assert_eq!(3, max_locals);
Ok(())
}
#[test]
fn test_virtual_with_long_parameter() -> Result<()> {
let max_locals = get_max_locals(MethodAccessFlags::empty(), "(J)V", &[])?;
assert_eq!(3, max_locals);
Ok(())
}
#[test]
fn test_instruction_int_constant() -> Result<()> {
let max_locals = get_max_locals(
MethodAccessFlags::empty(),
"()V",
&[Instruction::Iconst_0, Instruction::Iload_0],
)?;
assert_eq!(1, max_locals);
Ok(())
}
#[test]
fn test_instruction_long_constant() -> Result<()> {
let max_locals = get_max_locals(
MethodAccessFlags::empty(),
"()V",
&[Instruction::Lconst_0, Instruction::Lload_0],
)?;
assert_eq!(2, max_locals);
Ok(())
}
#[test]
fn test_parameters_and_instructions() -> Result<()> {
let instructions = [
Instruction::Iload_0,
Instruction::Lload_1,
Instruction::Return,
];
let max_locals = get_max_locals(MethodAccessFlags::empty(), "(IJ)V", &instructions)?;
assert_eq!(4, max_locals);
Ok(())
}
}