use crate::instruction::{Instruction, OpCode, Operand};
use super::slots::{
find_resolution_start_index, find_slot_store_before, local_load_index, previous_non_nop_index,
static_load_index, trace_stack_value_producer_before, SlotDomain,
};
pub(in crate::decompiler::analysis) fn calla_target_from_pusha(
instructions: &[Instruction],
index: usize,
) -> Option<usize> {
let mut cursor = index.checked_sub(1)?;
loop {
let prev = instructions.get(cursor)?;
if prev.opcode == OpCode::Nop {
cursor = cursor.checked_sub(1)?;
continue;
}
return trace_pointer_target_from_value_source(instructions, cursor);
}
}
pub(super) fn pusha_absolute_target(instruction: &Instruction) -> Option<usize> {
let delta = match instruction.operand {
Some(Operand::I32(value)) => value as isize,
_ => return None,
};
instruction.offset.checked_add_signed(delta)
}
pub(super) fn resolve_slot_pointer_target(
instructions: &[Instruction],
before_index: usize,
domain: SlotDomain,
) -> Option<usize> {
let store_index = find_slot_store_before(instructions, before_index, domain)?;
let source_index = previous_non_nop_index(instructions, store_index.checked_sub(1)?)?;
trace_pointer_target_from_value_source(instructions, source_index)
}
fn trace_pointer_target_from_value_source(
instructions: &[Instruction],
mut source_index: usize,
) -> Option<usize> {
loop {
let instruction = instructions.get(source_index)?;
if instruction.opcode == OpCode::Dup {
source_index = previous_non_nop_index(instructions, source_index.checked_sub(1)?)?;
continue;
}
if instruction.opcode == OpCode::PushA {
return pusha_absolute_target(instruction);
}
if instruction.opcode == OpCode::Pickitem {
return resolve_pickitem_pointer_target(instructions, source_index);
}
let domain = if let Some(slot) = local_load_index(instruction) {
SlotDomain::Local(slot)
} else {
SlotDomain::Static(static_load_index(instruction)?)
};
let store_index = find_slot_store_before(instructions, source_index, domain)?;
source_index = previous_non_nop_index(instructions, store_index.checked_sub(1)?)?;
}
}
fn resolve_pickitem_pointer_target(
instructions: &[Instruction],
pickitem_index: usize,
) -> Option<usize> {
let index_source = previous_non_nop_index(instructions, pickitem_index.checked_sub(1)?)?;
let array_source_index = previous_non_nop_index(instructions, index_source.checked_sub(1)?)?;
let domain = trace_container_domain_from_value_source(instructions, array_source_index)?;
let scan_start = match domain {
SlotDomain::Local(_) => find_resolution_start_index(instructions, pickitem_index),
SlotDomain::Static(_) => 0,
};
let mut resolved_target = None;
for (index, instruction) in instructions
.iter()
.enumerate()
.take(pickitem_index)
.skip(scan_start)
{
if instruction.opcode != OpCode::Append {
continue;
}
let item_index = trace_stack_value_producer_before(instructions, index, 0)?;
let array_index = trace_stack_value_producer_before(instructions, index, 1)?;
let Some(array_domain) =
trace_container_domain_from_value_source(instructions, array_index)
else {
continue;
};
if array_domain != domain {
continue;
}
let target = trace_pointer_target_from_value_source(instructions, item_index)?;
if let Some(existing) = resolved_target {
if existing != target {
return None;
}
} else {
resolved_target = Some(target);
}
}
resolved_target
}
fn trace_container_domain_from_value_source(
instructions: &[Instruction],
mut source_index: usize,
) -> Option<SlotDomain> {
loop {
let instruction = instructions.get(source_index)?;
if instruction.opcode == OpCode::Dup {
source_index = previous_non_nop_index(instructions, source_index.checked_sub(1)?)?;
continue;
}
if let Some(slot) = local_load_index(instruction) {
let domain = SlotDomain::Local(slot);
let Some(store_index) = find_slot_store_before(instructions, source_index, domain)
else {
return Some(domain);
};
let source = previous_non_nop_index(instructions, store_index.checked_sub(1)?)?;
let source_instruction = instructions.get(source)?;
if source_instruction.opcode == OpCode::Dup {
source_index = previous_non_nop_index(instructions, source.checked_sub(1)?)?;
continue;
}
if local_load_index(source_instruction).is_some()
|| static_load_index(source_instruction).is_some()
{
source_index = source;
continue;
}
return Some(domain);
}
if let Some(slot) = static_load_index(instruction) {
let domain = SlotDomain::Static(slot);
let Some(store_index) = find_slot_store_before(instructions, source_index, domain)
else {
return Some(domain);
};
let source = previous_non_nop_index(instructions, store_index.checked_sub(1)?)?;
let source_instruction = instructions.get(source)?;
if source_instruction.opcode == OpCode::Dup {
source_index = previous_non_nop_index(instructions, source.checked_sub(1)?)?;
continue;
}
if local_load_index(source_instruction).is_some()
|| static_load_index(source_instruction).is_some()
{
source_index = source;
continue;
}
return Some(domain);
}
return None;
}
}