use std::collections::{BTreeMap, HashSet};
use crate::decompiler::analysis::call_graph::{CallGraph, CallTarget};
use crate::instruction::Instruction;
use crate::manifest::ContractManifest;
use super::entry_stack::estimate_required_entry_stack_depth;
use super::methods::{
find_manifest_entry_method, initslot_argument_count_at, next_inferred_method_offset,
offset_as_usize,
};
#[must_use]
pub(in super::super) fn build_method_arg_counts_by_offset(
instructions: &[Instruction],
inferred_starts: &[usize],
manifest: Option<&ContractManifest>,
) -> BTreeMap<usize, usize> {
let mut counts = BTreeMap::new();
let entry_offset = instructions.first().map(|ins| ins.offset).unwrap_or(0);
let entry_method = manifest.and_then(|m| find_manifest_entry_method(m, entry_offset));
let use_manifest_entry = entry_method.is_some();
let entry_arg_count = if use_manifest_entry {
entry_method
.as_ref()
.map(|(method, _)| method.parameters.len())
.unwrap_or(0)
} else {
initslot_argument_count_at(instructions, entry_offset).unwrap_or(0)
};
counts.insert(entry_offset, entry_arg_count);
if let Some(manifest) = manifest {
for method in &manifest.abi.methods {
if let Some(start) = offset_as_usize(method.offset) {
counts.insert(start, method.parameters.len());
}
}
}
let manifest_offsets: HashSet<usize> = manifest
.map(|m| {
m.abi
.methods
.iter()
.filter_map(|method| offset_as_usize(method.offset))
.collect()
})
.unwrap_or_default();
for start in inferred_starts {
if manifest_offsets.contains(start) {
continue;
}
let arg_count = initslot_argument_count_at(instructions, *start)
.or_else(|| {
infer_entry_stack_arg_count_for_inferred_start(
instructions,
inferred_starts,
*start,
)
})
.unwrap_or(0);
counts.insert(*start, arg_count);
}
counts
}
fn infer_entry_stack_arg_count_for_inferred_start(
instructions: &[Instruction],
inferred_starts: &[usize],
start: usize,
) -> Option<usize> {
let end = next_inferred_method_offset(inferred_starts, start)
.or_else(|| instructions.last().map(|ins| ins.offset + 1))
.unwrap_or(start);
let lo = instructions.partition_point(|ins| ins.offset < start);
let hi = instructions.partition_point(|ins| ins.offset < end);
estimate_required_entry_stack_depth(&instructions[lo..hi])
}
#[must_use]
pub(in super::super) fn build_calla_targets_by_offset(
call_graph: &CallGraph,
) -> BTreeMap<usize, usize> {
let mut targets = BTreeMap::new();
for edge in &call_graph.edges {
if edge.opcode != "CALLA" {
continue;
}
if let CallTarget::Internal { method } = &edge.target {
targets.insert(edge.call_offset, method.offset);
}
}
targets
}
#[must_use]
pub(in super::super) fn build_call_targets_by_offset(
call_graph: &CallGraph,
) -> BTreeMap<usize, usize> {
let mut targets = BTreeMap::new();
for edge in &call_graph.edges {
if edge.opcode != "CALL" && edge.opcode != "CALL_L" {
continue;
}
if let CallTarget::Internal { method } = &edge.target {
targets.insert(edge.call_offset, method.offset);
}
}
targets
}