Skip to main content

neo_decompiler/decompiler/analysis/
methods.rs

1use std::collections::BTreeMap;
2
3use serde::Serialize;
4
5use crate::instruction::Instruction;
6use crate::manifest::{ContractManifest, ManifestMethod};
7
8use super::super::helpers::{find_manifest_entry_method, sanitize_identifier};
9
10/// Reference to a (possibly inferred) method within a script.
11///
12/// When a manifest is present, `name` typically matches the ABI method name.
13/// For internal helper routines without ABI metadata, `name` will be a
14/// synthetic `sub_0x....` label.
15#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize)]
16pub struct MethodRef {
17    /// Method entry offset in bytecode.
18    pub offset: usize,
19    /// Human-readable method name.
20    pub name: String,
21}
22
23impl MethodRef {
24    pub(super) fn synthetic(offset: usize) -> Self {
25        Self {
26            offset,
27            name: format!("sub_0x{offset:04X}"),
28        }
29    }
30}
31
32#[derive(Debug, Clone)]
33pub(super) struct MethodSpan {
34    pub(super) start: usize,
35    pub(super) end: usize,
36    pub(super) method: MethodRef,
37}
38
39/// Helper for mapping bytecode offsets to method ranges.
40#[derive(Debug, Clone)]
41pub struct MethodTable {
42    spans: Vec<MethodSpan>,
43    manifest_index_by_start: BTreeMap<usize, usize>,
44}
45
46impl MethodTable {
47    /// Build a method table using the manifest ABI offsets when present.
48    ///
49    /// If the manifest does not cover the script entry region, a synthetic
50    /// entry span is inserted to ensure that every offset resolves to some
51    /// method.
52    #[must_use]
53    pub fn new(instructions: &[Instruction], manifest: Option<&ContractManifest>) -> Self {
54        let script_start = instructions.first().map(|ins| ins.offset).unwrap_or(0);
55        let script_end = instructions
56            .last()
57            .map(|ins| ins.offset.saturating_add(1))
58            .unwrap_or(script_start);
59
60        let mut spans = Vec::new();
61        let mut manifest_index_by_start = BTreeMap::new();
62
63        if let Some(manifest) = manifest {
64            let mut methods: Vec<(usize, usize, &ManifestMethod)> = manifest
65                .abi
66                .methods
67                .iter()
68                .enumerate()
69                .filter_map(|(idx, method)| method.offset.map(|off| (off as usize, idx, method)))
70                .collect();
71            methods.sort_by_key(|(off, _, _)| *off);
72
73            for (pos, (start, idx, method)) in methods.iter().enumerate() {
74                let end = methods
75                    .get(pos + 1)
76                    .map(|(next, _, _)| *next)
77                    .unwrap_or(script_end);
78                let name = sanitize_identifier(&method.name);
79                spans.push(MethodSpan {
80                    start: *start,
81                    end,
82                    method: MethodRef {
83                        offset: *start,
84                        name,
85                    },
86                });
87                manifest_index_by_start.insert(*start, *idx);
88            }
89
90            let entry_name = find_manifest_entry_method(manifest, script_start)
91                .map(|(method, _)| sanitize_identifier(&method.name))
92                .unwrap_or_else(|| "script_entry".to_string());
93
94            let needs_entry = spans
95                .first()
96                .map(|span| span.start > script_start)
97                .unwrap_or(true);
98            if needs_entry {
99                let end = spans.first().map(|span| span.start).unwrap_or(script_end);
100                spans.insert(
101                    0,
102                    MethodSpan {
103                        start: script_start,
104                        end,
105                        method: MethodRef {
106                            offset: script_start,
107                            name: entry_name,
108                        },
109                    },
110                );
111            }
112        } else {
113            spans.push(MethodSpan {
114                start: script_start,
115                end: script_end,
116                method: MethodRef {
117                    offset: script_start,
118                    name: "script_entry".to_string(),
119                },
120            });
121        }
122
123        spans.sort_by_key(|span| span.start);
124
125        Self {
126            spans,
127            manifest_index_by_start,
128        }
129    }
130
131    /// Return all known method spans ordered by start offset.
132    pub(super) fn spans(&self) -> &[MethodSpan] {
133        &self.spans
134    }
135
136    /// Resolve the method that contains the given bytecode offset.
137    #[must_use]
138    pub fn method_for_offset(&self, offset: usize) -> MethodRef {
139        match self.spans.binary_search_by_key(&offset, |span| span.start) {
140            Ok(index) => self.spans[index].method.clone(),
141            Err(0) => self
142                .spans
143                .first()
144                .map(|span| span.method.clone())
145                .unwrap_or_else(|| MethodRef::synthetic(offset)),
146            Err(index) => {
147                let span = &self.spans[index - 1];
148                span.method.clone()
149            }
150        }
151    }
152
153    /// Resolve an internal call target to a method reference.
154    #[must_use]
155    pub fn resolve_internal_target(&self, target_offset: usize) -> MethodRef {
156        self.spans
157            .iter()
158            .find(|span| span.start == target_offset)
159            .map(|span| span.method.clone())
160            .unwrap_or_else(|| MethodRef::synthetic(target_offset))
161    }
162
163    /// Return the manifest ABI method index for a method starting at `offset`, if any.
164    #[must_use]
165    pub fn manifest_index_for_start(&self, offset: usize) -> Option<usize> {
166        self.manifest_index_by_start.get(&offset).copied()
167    }
168}