neo-decompiler 0.11.0

Neo N3 NEF decompiler: parse, disassemble, lift bytecode to high-level pseudocode and C# skeletons, with a CLI, JSON reports, and optional WebAssembly bindings.
Documentation
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,
};

/// Build method argument counts keyed by method start offset.
///
/// This is shared by high-level and C# lift renderers so both views infer the
/// same entry stack arity for detached helper chunks.
#[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])
}

/// Build `CALLA`-call-offset → target-method-start-offset, shared by the
/// high-level and C# renderers (the two copies were byte-identical).
#[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
}

/// Build `CALL`/`CALL_L`-call-offset → target-method-start-offset, shared by
/// the high-level and C# renderers (the two copies were byte-identical).
#[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
}