neo_decompiler/decompiler/analysis/
methods.rs1use 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#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize)]
16pub struct MethodRef {
17 pub offset: usize,
19 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#[derive(Debug, Clone)]
41pub struct MethodTable {
42 spans: Vec<MethodSpan>,
43 manifest_index_by_start: BTreeMap<usize, usize>,
44}
45
46impl MethodTable {
47 #[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 pub(super) fn spans(&self) -> &[MethodSpan] {
133 &self.spans
134 }
135
136 #[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 #[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 #[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}