use crate::Error::InternalError;
use crate::Result;
use crate::control_flow_graph::instruction;
use crate::control_flow_graph::type_stack::TypeStack;
use cranelift::prelude::{Block, FunctionBuilder, Type};
use ristretto_classfile::ConstantPool;
use ristretto_classfile::attributes::{ExceptionTableEntry, Instruction};
use std::collections::HashMap;
#[expect(clippy::too_many_lines)]
pub(crate) fn get_blocks(
function_builder: &mut FunctionBuilder,
constant_pool: &ConstantPool,
instructions: &[Instruction],
exception_table: &[ExceptionTableEntry],
) -> Result<HashMap<usize, Block>> {
let mut blocks = HashMap::new();
let mut stack_states: HashMap<usize, TypeStack> = HashMap::new();
let exception_handler_addresses = exception_table
.iter()
.map(|entry| usize::from(entry.handler_pc))
.collect::<Vec<_>>();
let mut stack = TypeStack::new();
blocks.insert(0, function_builder.create_block());
stack_states.insert(0, stack.clone());
for (program_counter, instruction) in instructions.iter().enumerate() {
if exception_handler_addresses.contains(&program_counter) {
stack.push_object()?;
insert_stack(&mut stack_states, program_counter, &stack)?;
create_block_with_parameters(
function_builder,
&stack_states,
program_counter,
&mut blocks,
);
}
if let Some(new_stack) = stack_states.get(&program_counter) {
stack = new_stack.clone();
}
instruction::simulate(&mut stack, constant_pool, instruction)?;
match instruction {
Instruction::Ifeq(address)
| Instruction::Ifne(address)
| Instruction::Iflt(address)
| Instruction::Ifge(address)
| Instruction::Ifgt(address)
| Instruction::Ifle(address)
| Instruction::If_icmpeq(address)
| Instruction::If_icmpne(address)
| Instruction::If_icmplt(address)
| Instruction::If_icmpge(address)
| Instruction::If_icmpgt(address)
| Instruction::If_icmple(address)
| Instruction::If_acmpeq(address)
| Instruction::If_acmpne(address)
| Instruction::Ifnull(address)
| Instruction::Ifnonnull(address) => {
let then_address = usize::from(*address);
insert_stack(&mut stack_states, then_address, &stack)?;
create_block_with_parameters(
function_builder,
&stack_states,
then_address,
&mut blocks,
);
let Some(else_address) = program_counter.checked_add(1) else {
return Err(InternalError(format!(
"Address overflow: {program_counter} + 1"
)));
};
insert_stack(&mut stack_states, else_address, &stack)?;
create_block_with_parameters(
function_builder,
&stack_states,
else_address,
&mut blocks,
);
}
Instruction::Goto(address) => {
let address = usize::from(*address);
insert_stack(&mut stack_states, address, &stack)?;
create_block_with_parameters(function_builder, &stack_states, address, &mut blocks);
}
Instruction::Goto_w(address) => {
let address = usize::try_from(*address)?;
insert_stack(&mut stack_states, address, &stack)?;
create_block_with_parameters(function_builder, &stack_states, address, &mut blocks);
}
Instruction::Jsr(address) => {
let Some(next_address) = program_counter.checked_add(1) else {
return Err(InternalError(format!(
"Address overflow: {program_counter} + 1"
)));
};
insert_stack(&mut stack_states, next_address, &stack)?;
create_block_with_parameters(
function_builder,
&stack_states,
next_address,
&mut blocks,
);
let address = usize::from(*address);
insert_stack(&mut stack_states, address, &stack)?;
create_block_with_parameters(function_builder, &stack_states, address, &mut blocks);
}
Instruction::Jsr_w(address) => {
let Some(next_address) = program_counter.checked_add(1) else {
return Err(InternalError(format!(
"Address overflow: {program_counter} + 1"
)));
};
insert_stack(&mut stack_states, next_address, &stack)?;
create_block_with_parameters(
function_builder,
&stack_states,
next_address,
&mut blocks,
);
let address = usize::try_from(*address)?;
insert_stack(&mut stack_states, address, &stack)?;
create_block_with_parameters(function_builder, &stack_states, address, &mut blocks);
}
Instruction::Tableswitch(table_switch) => {
let default = usize::try_from(
i32::try_from(program_counter)?.wrapping_add(table_switch.default),
)?;
insert_stack(&mut stack_states, default, &stack)?;
create_block_with_parameters(function_builder, &stack_states, default, &mut blocks);
for offset in &table_switch.offsets {
let address =
usize::try_from(i32::try_from(program_counter)?.wrapping_add(*offset))?;
insert_stack(&mut stack_states, address, &stack)?;
create_block_with_parameters(
function_builder,
&stack_states,
address,
&mut blocks,
);
}
}
Instruction::Lookupswitch(lookup_switch) => {
let default = usize::try_from(
i32::try_from(program_counter)?.wrapping_add(lookup_switch.default),
)?;
insert_stack(&mut stack_states, default, &stack)?;
create_block_with_parameters(function_builder, &stack_states, default, &mut blocks);
for (_key, offset) in &lookup_switch.pairs {
let address =
usize::try_from(i32::try_from(program_counter)?.wrapping_add(*offset))?;
insert_stack(&mut stack_states, address, &stack)?;
create_block_with_parameters(
function_builder,
&stack_states,
address,
&mut blocks,
);
}
}
_ => {}
}
}
Ok(blocks)
}
pub(crate) fn insert_stack(
stack_states: &mut HashMap<usize, TypeStack>,
address: usize,
stack: &TypeStack,
) -> Result<()> {
match stack_states.get(&address) {
Some(entry_stack) => {
if entry_stack != stack {
return Err(InternalError(format!(
"Invalid stack state for address {address}, entry_stack={entry_stack:?} and stack={stack:?}"
)));
}
}
None => {
stack_states.insert(address, stack.clone());
}
}
Ok(())
}
pub(crate) fn create_block_with_parameters(
function_builder: &mut FunctionBuilder,
stack_states: &HashMap<usize, TypeStack>,
address: usize,
blocks: &mut HashMap<usize, Block>,
) {
blocks.entry(address).or_insert_with(|| {
let block = function_builder.create_block();
if let Some(stack_types) = stack_states.get(&address) {
let stack_types = stack_types.to_vec();
append_block_params(function_builder, block, &stack_types);
}
block
});
}
pub(crate) fn append_block_params(
function_builder: &mut FunctionBuilder,
block: Block,
types: &[Type],
) {
for value_type in types {
function_builder.append_block_param(block, *value_type);
}
}
#[cfg(test)]
mod tests {
use super::*;
use cranelift::codegen::ir::Function;
use cranelift::prelude::*;
#[test]
fn test_blocks_for_if_comparison_with_goto() -> Result<()> {
let constant_pool = ConstantPool::new();
let mut function_context = FunctionBuilderContext::new();
let mut function = Function::new();
let mut function_builder = FunctionBuilder::new(&mut function, &mut function_context);
let instructions = vec![
Instruction::Iload_0, Instruction::Iload_1, Instruction::If_icmplt(5), Instruction::Iload_0, Instruction::Goto(6), Instruction::Iload_1, Instruction::Ireturn, ];
let exception_table = Vec::new();
let blocks = get_blocks(
&mut function_builder,
&constant_pool,
&instructions,
&exception_table,
)?;
assert_eq!(blocks.len(), 4);
let _block_0 = blocks.get(&0).expect("block0");
let _block_3 = blocks.get(&3).expect("block3");
let _block_5 = blocks.get(&5).expect("block5");
let _block_6 = blocks.get(&6).expect("block6");
Ok(())
}
}