1use serde::Serialize;
8
9use crate::instruction::{Instruction, OpCode, Operand};
10use crate::manifest::ContractManifest;
11
12use super::{MethodRef, MethodTable};
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize)]
16#[non_exhaustive]
17pub enum SlotKind {
18 #[serde(rename = "local")]
20 Local,
21 #[serde(rename = "argument")]
23 Argument,
24 #[serde(rename = "static")]
26 Static,
27}
28
29#[derive(Debug, Clone, Default, Serialize)]
31pub struct SlotXref {
32 pub index: usize,
34 pub reads: Vec<usize>,
36 pub writes: Vec<usize>,
38}
39
40#[derive(Debug, Clone, Serialize)]
42pub struct MethodXrefs {
43 pub method: MethodRef,
45 pub locals: Vec<SlotXref>,
47 pub arguments: Vec<SlotXref>,
49 pub statics: Vec<SlotXref>,
51}
52
53#[derive(Debug, Clone, Serialize)]
55pub struct Xrefs {
56 pub methods: Vec<MethodXrefs>,
58}
59
60#[must_use]
62pub fn build_xrefs(instructions: &[Instruction], manifest: Option<&ContractManifest>) -> Xrefs {
63 let table = MethodTable::new(instructions, manifest);
64 let static_count = scan_static_slot_count(instructions).unwrap_or(0);
65
66 let mut methods = Vec::new();
67 for span in table.spans() {
68 let slice: Vec<&Instruction> = instructions
69 .iter()
70 .filter(|ins| ins.offset >= span.start && ins.offset < span.end)
71 .collect();
72 let (locals_count, args_count) = scan_slot_counts(&slice).unwrap_or((0, 0));
73
74 let mut method_xrefs = MethodXrefs {
75 method: span.method.clone(),
76 locals: (0..locals_count)
77 .map(|index| SlotXref {
78 index,
79 ..SlotXref::default()
80 })
81 .collect(),
82 arguments: (0..args_count)
83 .map(|index| SlotXref {
84 index,
85 ..SlotXref::default()
86 })
87 .collect(),
88 statics: (0..static_count)
89 .map(|index| SlotXref {
90 index,
91 ..SlotXref::default()
92 })
93 .collect(),
94 };
95
96 for instr in &slice {
97 if let Some((kind, index, is_write)) = slot_access(instr) {
98 let target = match kind {
99 SlotKind::Local => &mut method_xrefs.locals,
100 SlotKind::Argument => &mut method_xrefs.arguments,
101 SlotKind::Static => &mut method_xrefs.statics,
102 };
103 if index >= target.len() {
104 let start = target.len();
105 target.extend((start..=index).map(|idx| SlotXref {
106 index: idx,
107 ..SlotXref::default()
108 }));
109 }
110 let entry = &mut target[index];
111 entry.index = index;
112 if is_write {
113 entry.writes.push(instr.offset);
114 } else {
115 entry.reads.push(instr.offset);
116 }
117 }
118 }
119
120 methods.push(method_xrefs);
121 }
122
123 Xrefs { methods }
124}
125
126fn slot_access(instr: &Instruction) -> Option<(SlotKind, usize, bool)> {
127 use OpCode::*;
128
129 match instr.opcode {
130 Ldloc0 => Some((SlotKind::Local, 0, false)),
132 Ldloc1 => Some((SlotKind::Local, 1, false)),
133 Ldloc2 => Some((SlotKind::Local, 2, false)),
134 Ldloc3 => Some((SlotKind::Local, 3, false)),
135 Ldloc4 => Some((SlotKind::Local, 4, false)),
136 Ldloc5 => Some((SlotKind::Local, 5, false)),
137 Ldloc6 => Some((SlotKind::Local, 6, false)),
138 Ldloc => slot_from_operand(SlotKind::Local, instr.operand.as_ref(), false),
139 Stloc0 => Some((SlotKind::Local, 0, true)),
140 Stloc1 => Some((SlotKind::Local, 1, true)),
141 Stloc2 => Some((SlotKind::Local, 2, true)),
142 Stloc3 => Some((SlotKind::Local, 3, true)),
143 Stloc4 => Some((SlotKind::Local, 4, true)),
144 Stloc5 => Some((SlotKind::Local, 5, true)),
145 Stloc6 => Some((SlotKind::Local, 6, true)),
146 Stloc => slot_from_operand(SlotKind::Local, instr.operand.as_ref(), true),
147
148 Ldarg0 => Some((SlotKind::Argument, 0, false)),
150 Ldarg1 => Some((SlotKind::Argument, 1, false)),
151 Ldarg2 => Some((SlotKind::Argument, 2, false)),
152 Ldarg3 => Some((SlotKind::Argument, 3, false)),
153 Ldarg4 => Some((SlotKind::Argument, 4, false)),
154 Ldarg5 => Some((SlotKind::Argument, 5, false)),
155 Ldarg6 => Some((SlotKind::Argument, 6, false)),
156 Ldarg => slot_from_operand(SlotKind::Argument, instr.operand.as_ref(), false),
157 Starg0 => Some((SlotKind::Argument, 0, true)),
158 Starg1 => Some((SlotKind::Argument, 1, true)),
159 Starg2 => Some((SlotKind::Argument, 2, true)),
160 Starg3 => Some((SlotKind::Argument, 3, true)),
161 Starg4 => Some((SlotKind::Argument, 4, true)),
162 Starg5 => Some((SlotKind::Argument, 5, true)),
163 Starg6 => Some((SlotKind::Argument, 6, true)),
164 Starg => slot_from_operand(SlotKind::Argument, instr.operand.as_ref(), true),
165
166 Ldsfld0 => Some((SlotKind::Static, 0, false)),
168 Ldsfld1 => Some((SlotKind::Static, 1, false)),
169 Ldsfld2 => Some((SlotKind::Static, 2, false)),
170 Ldsfld3 => Some((SlotKind::Static, 3, false)),
171 Ldsfld4 => Some((SlotKind::Static, 4, false)),
172 Ldsfld5 => Some((SlotKind::Static, 5, false)),
173 Ldsfld6 => Some((SlotKind::Static, 6, false)),
174 Ldsfld => slot_from_operand(SlotKind::Static, instr.operand.as_ref(), false),
175 Stsfld0 => Some((SlotKind::Static, 0, true)),
176 Stsfld1 => Some((SlotKind::Static, 1, true)),
177 Stsfld2 => Some((SlotKind::Static, 2, true)),
178 Stsfld3 => Some((SlotKind::Static, 3, true)),
179 Stsfld4 => Some((SlotKind::Static, 4, true)),
180 Stsfld5 => Some((SlotKind::Static, 5, true)),
181 Stsfld6 => Some((SlotKind::Static, 6, true)),
182 Stsfld => slot_from_operand(SlotKind::Static, instr.operand.as_ref(), true),
183
184 _ => None,
185 }
186}
187
188fn slot_from_operand(
189 kind: SlotKind,
190 operand: Option<&Operand>,
191 is_write: bool,
192) -> Option<(SlotKind, usize, bool)> {
193 let Some(Operand::U8(index)) = operand else {
194 return None;
195 };
196 Some((kind, *index as usize, is_write))
197}
198
199fn scan_slot_counts(instructions: &[&Instruction]) -> Option<(usize, usize)> {
200 for instr in instructions {
201 if instr.opcode != OpCode::Initslot {
202 continue;
203 }
204 if let Some(Operand::Bytes(bytes)) = &instr.operand {
205 if bytes.len() == 2 {
206 return Some((bytes[0] as usize, bytes[1] as usize));
207 }
208 }
209 }
210 None
211}
212
213fn scan_static_slot_count(instructions: &[Instruction]) -> Option<usize> {
214 for instr in instructions {
215 if instr.opcode != OpCode::Initsslot {
216 continue;
217 }
218 if let Some(Operand::U8(count)) = &instr.operand {
219 return Some(*count as usize);
220 }
221 }
222 None
223}