use std::collections::{BTreeSet, HashMap};
use crate::instruction::{Instruction, OpCode, Operand};
use crate::manifest::ContractManifest;
use super::control_flow::{collect_control_flow_edges, relative_target};
use super::manifest::offset_as_usize;
#[must_use]
pub(in super::super::super) fn inferred_method_starts(
instructions: &[Instruction],
manifest: Option<&ContractManifest>,
) -> Vec<usize> {
let mut starts = BTreeSet::new();
if let Some(entry) = instructions.first() {
starts.insert(entry.offset);
}
if let Some(manifest) = manifest {
starts.extend(
manifest
.abi
.methods
.iter()
.filter_map(|method| offset_as_usize(method.offset)),
);
}
starts.extend(collect_initslot_offsets(instructions));
starts.extend(collect_call_targets(instructions));
let baseline_starts = starts.clone();
starts.extend(collect_post_ret_method_offsets(
instructions,
&baseline_starts,
));
starts.into_iter().collect()
}
#[must_use]
pub(in super::super::super) fn next_inferred_method_offset(
starts: &[usize],
start: usize,
) -> Option<usize> {
starts.iter().copied().find(|offset| *offset > start)
}
#[must_use]
pub(in super::super::super) fn initslot_argument_count_at(
instructions: &[Instruction],
start: usize,
) -> Option<usize> {
instructions
.iter()
.find(|ins| ins.offset == start && ins.opcode == OpCode::Initslot)
.and_then(|ins| match &ins.operand {
Some(Operand::Bytes(bytes)) if bytes.len() >= 2 => Some(bytes[1] as usize),
_ => None,
})
}
pub(in super::super::super) fn collect_initslot_offsets(
instructions: &[Instruction],
) -> Vec<usize> {
let mut offsets = instructions
.iter()
.filter(|ins| matches!(ins.opcode, OpCode::Initslot))
.map(|ins| ins.offset)
.collect::<Vec<_>>();
offsets.sort_unstable();
offsets.dedup();
offsets
}
pub(in super::super::super) fn collect_call_targets(instructions: &[Instruction]) -> Vec<usize> {
let known_offsets: BTreeSet<usize> = instructions.iter().map(|ins| ins.offset).collect();
let mut targets = Vec::new();
for instruction in instructions {
if matches!(instruction.opcode, OpCode::Call | OpCode::Call_L) {
if let Some(target) = relative_target(instruction, &known_offsets) {
targets.push(target);
}
}
}
targets.sort_unstable();
targets.dedup();
targets
}
fn collect_post_ret_method_offsets(
instructions: &[Instruction],
baseline_starts: &BTreeSet<usize>,
) -> Vec<usize> {
let known_offsets: BTreeSet<usize> = instructions.iter().map(|ins| ins.offset).collect();
let control_flow_edges = collect_control_flow_edges(instructions, &known_offsets);
let method_range = |offset: usize| -> (usize, usize) {
let start = baseline_starts
.range(..=offset)
.next_back()
.copied()
.unwrap_or(offset);
let end = baseline_starts
.range((start + 1)..)
.next()
.copied()
.unwrap_or(usize::MAX);
(start, end)
};
let mut edges_by_target: HashMap<usize, Vec<usize>> = HashMap::new();
let mut max_forward_in_method: HashMap<usize, usize> = HashMap::new();
for &(source, target) in &control_flow_edges {
edges_by_target.entry(target).or_default().push(source);
let (m_start, m_end) = method_range(source);
if target < m_end {
max_forward_in_method
.entry(m_start)
.and_modify(|t| *t = (*t).max(target))
.or_insert(target);
}
}
let mut starts = instructions
.windows(2)
.filter_map(|pair| {
let current = &pair[0];
if !matches!(
current.opcode,
OpCode::Ret | OpCode::Throw | OpCode::Abort | OpCode::Abortmsg
) {
return None;
}
let next = &pair[1];
let (method_start, method_end) = method_range(current.offset);
let incoming = edges_by_target.get(&next.offset);
let has_incoming_from_same_baseline_method = incoming.is_some_and(|sources| {
sources.iter().any(|&s| s >= method_start && s < method_end)
});
let has_incoming_from_other_baseline_method = incoming.is_some_and(|sources| {
sources.iter().any(|&s| s < method_start || s >= method_end)
});
let has_same_baseline_incoming_later_in_range = max_forward_in_method
.get(&method_start)
.is_some_and(|&max_target| max_target > next.offset);
let detached_tail_after_terminator = has_incoming_from_other_baseline_method
|| (!has_incoming_from_same_baseline_method
&& !has_same_baseline_incoming_later_in_range);
detached_tail_after_terminator.then_some(next.offset)
})
.collect::<Vec<_>>();
starts.sort_unstable();
starts.dedup();
starts
}