use crate::Error::InternalError;
use crate::Result;
use crate::control_flow_graph::instruction;
use crate::control_flow_graph::type_stack::TypeStack;
use ahash::AHashMap;
use cranelift::prelude::{Block, FunctionBuilder, Type};
use ristretto_classfile::ConstantPool;
use ristretto_classfile::attributes::{ExceptionTableEntry, Instruction};
fn has_backward_jump_to_zero(instructions: &[Instruction]) -> bool {
for (program_counter, instruction) in instructions.iter().enumerate() {
if program_counter == 0 {
continue;
}
let target_address = match instruction {
Instruction::Goto_w(address) => usize::try_from(*address).ok(),
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)
| Instruction::Goto(address) => Some(usize::from(*address)),
_ => None,
};
if target_address == Some(0) {
return true;
}
}
false
}
#[expect(clippy::too_many_lines)]
pub(crate) fn get_blocks(
function_builder: &mut FunctionBuilder,
constant_pool: &ConstantPool,
instructions: &[Instruction],
exception_table: &[ExceptionTableEntry],
) -> Result<(Block, AHashMap<usize, Block>)> {
let mut blocks = AHashMap::default();
let mut stack_states: AHashMap<usize, TypeStack> = AHashMap::default();
let exception_handler_addresses = exception_table
.iter()
.map(|entry| usize::from(entry.handler_pc))
.collect::<Vec<_>>();
let mut stack = TypeStack::new();
let mut in_dead_code = false;
let entry_block = function_builder.create_block();
let has_loop_to_zero = has_backward_jump_to_zero(instructions);
if has_loop_to_zero {
blocks.insert(0, function_builder.create_block());
} else {
blocks.insert(0, entry_block);
}
stack_states.insert(0, stack.clone());
for (program_counter, instruction) in instructions.iter().enumerate() {
if exception_handler_addresses.contains(&program_counter) {
in_dead_code = false;
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();
in_dead_code = false;
} else if in_dead_code {
continue;
}
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,
);
}
}
_ => {}
}
if matches!(
instruction,
Instruction::Goto(..)
| Instruction::Goto_w(..)
| Instruction::Tableswitch { .. }
| Instruction::Lookupswitch { .. }
| Instruction::Ret(..)
| Instruction::Ret_w(..)
| Instruction::Return
| Instruction::Ireturn
| Instruction::Lreturn
| Instruction::Freturn
| Instruction::Dreturn
| Instruction::Areturn
| Instruction::Athrow
) {
in_dead_code = true;
}
}
Ok((entry_block, blocks))
}
pub(crate) fn insert_stack(
stack_states: &mut AHashMap<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: &AHashMap<usize, TypeStack>,
address: usize,
blocks: &mut AHashMap<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 (_entry_block, 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(())
}
}