use serde::Serialize;
use crate::instruction::{Instruction, OpCode, Operand};
use crate::manifest::ContractManifest;
use super::{MethodRef, MethodTable};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize)]
#[non_exhaustive]
pub enum SlotKind {
#[serde(rename = "local")]
Local,
#[serde(rename = "argument")]
Argument,
#[serde(rename = "static")]
Static,
}
#[derive(Debug, Clone, Default, Serialize)]
pub struct SlotXref {
pub index: usize,
pub reads: Vec<usize>,
pub writes: Vec<usize>,
}
#[derive(Debug, Clone, Serialize)]
pub struct MethodXrefs {
pub method: MethodRef,
pub locals: Vec<SlotXref>,
pub arguments: Vec<SlotXref>,
pub statics: Vec<SlotXref>,
}
#[derive(Debug, Clone, Serialize)]
pub struct Xrefs {
pub methods: Vec<MethodXrefs>,
}
#[must_use]
pub fn build_xrefs(instructions: &[Instruction], manifest: Option<&ContractManifest>) -> Xrefs {
let table = MethodTable::new(instructions, manifest);
let static_count = scan_static_slot_count(instructions).unwrap_or(0);
let mut methods = Vec::new();
let mut cursor = 0usize;
for span in table.spans() {
while cursor < instructions.len() && instructions[cursor].offset < span.start {
cursor += 1;
}
let begin = cursor;
while cursor < instructions.len() && instructions[cursor].offset < span.end {
cursor += 1;
}
let slice: Vec<&Instruction> = instructions[begin..cursor].iter().collect();
let (locals_count, args_count) = scan_slot_counts(&slice).unwrap_or((0, 0));
let mut method_xrefs = MethodXrefs {
method: span.method.clone(),
locals: (0..locals_count)
.map(|index| SlotXref {
index,
..SlotXref::default()
})
.collect(),
arguments: (0..args_count)
.map(|index| SlotXref {
index,
..SlotXref::default()
})
.collect(),
statics: (0..static_count)
.map(|index| SlotXref {
index,
..SlotXref::default()
})
.collect(),
};
for instr in &slice {
if let Some((kind, index, is_write)) = slot_access(instr) {
let target = match kind {
SlotKind::Local => &mut method_xrefs.locals,
SlotKind::Argument => &mut method_xrefs.arguments,
SlotKind::Static => &mut method_xrefs.statics,
};
if index >= target.len() {
let start = target.len();
target.extend((start..=index).map(|idx| SlotXref {
index: idx,
..SlotXref::default()
}));
}
let entry = &mut target[index];
entry.index = index;
if is_write {
entry.writes.push(instr.offset);
} else {
entry.reads.push(instr.offset);
}
}
}
methods.push(method_xrefs);
}
Xrefs { methods }
}
fn slot_access(instr: &Instruction) -> Option<(SlotKind, usize, bool)> {
use OpCode::*;
match instr.opcode {
Ldloc0 => Some((SlotKind::Local, 0, false)),
Ldloc1 => Some((SlotKind::Local, 1, false)),
Ldloc2 => Some((SlotKind::Local, 2, false)),
Ldloc3 => Some((SlotKind::Local, 3, false)),
Ldloc4 => Some((SlotKind::Local, 4, false)),
Ldloc5 => Some((SlotKind::Local, 5, false)),
Ldloc6 => Some((SlotKind::Local, 6, false)),
Ldloc => slot_from_operand(SlotKind::Local, instr.operand.as_ref(), false),
Stloc0 => Some((SlotKind::Local, 0, true)),
Stloc1 => Some((SlotKind::Local, 1, true)),
Stloc2 => Some((SlotKind::Local, 2, true)),
Stloc3 => Some((SlotKind::Local, 3, true)),
Stloc4 => Some((SlotKind::Local, 4, true)),
Stloc5 => Some((SlotKind::Local, 5, true)),
Stloc6 => Some((SlotKind::Local, 6, true)),
Stloc => slot_from_operand(SlotKind::Local, instr.operand.as_ref(), true),
Ldarg0 => Some((SlotKind::Argument, 0, false)),
Ldarg1 => Some((SlotKind::Argument, 1, false)),
Ldarg2 => Some((SlotKind::Argument, 2, false)),
Ldarg3 => Some((SlotKind::Argument, 3, false)),
Ldarg4 => Some((SlotKind::Argument, 4, false)),
Ldarg5 => Some((SlotKind::Argument, 5, false)),
Ldarg6 => Some((SlotKind::Argument, 6, false)),
Ldarg => slot_from_operand(SlotKind::Argument, instr.operand.as_ref(), false),
Starg0 => Some((SlotKind::Argument, 0, true)),
Starg1 => Some((SlotKind::Argument, 1, true)),
Starg2 => Some((SlotKind::Argument, 2, true)),
Starg3 => Some((SlotKind::Argument, 3, true)),
Starg4 => Some((SlotKind::Argument, 4, true)),
Starg5 => Some((SlotKind::Argument, 5, true)),
Starg6 => Some((SlotKind::Argument, 6, true)),
Starg => slot_from_operand(SlotKind::Argument, instr.operand.as_ref(), true),
Ldsfld0 => Some((SlotKind::Static, 0, false)),
Ldsfld1 => Some((SlotKind::Static, 1, false)),
Ldsfld2 => Some((SlotKind::Static, 2, false)),
Ldsfld3 => Some((SlotKind::Static, 3, false)),
Ldsfld4 => Some((SlotKind::Static, 4, false)),
Ldsfld5 => Some((SlotKind::Static, 5, false)),
Ldsfld6 => Some((SlotKind::Static, 6, false)),
Ldsfld => slot_from_operand(SlotKind::Static, instr.operand.as_ref(), false),
Stsfld0 => Some((SlotKind::Static, 0, true)),
Stsfld1 => Some((SlotKind::Static, 1, true)),
Stsfld2 => Some((SlotKind::Static, 2, true)),
Stsfld3 => Some((SlotKind::Static, 3, true)),
Stsfld4 => Some((SlotKind::Static, 4, true)),
Stsfld5 => Some((SlotKind::Static, 5, true)),
Stsfld6 => Some((SlotKind::Static, 6, true)),
Stsfld => slot_from_operand(SlotKind::Static, instr.operand.as_ref(), true),
_ => None,
}
}
fn slot_from_operand(
kind: SlotKind,
operand: Option<&Operand>,
is_write: bool,
) -> Option<(SlotKind, usize, bool)> {
let Some(Operand::U8(index)) = operand else {
return None;
};
Some((kind, *index as usize, is_write))
}
fn scan_slot_counts(instructions: &[&Instruction]) -> Option<(usize, usize)> {
for instr in instructions {
if instr.opcode != OpCode::Initslot {
continue;
}
if let Some(Operand::Bytes(bytes)) = &instr.operand {
if bytes.len() == 2 {
return Some((bytes[0] as usize, bytes[1] as usize));
}
}
}
None
}
fn scan_static_slot_count(instructions: &[Instruction]) -> Option<usize> {
for instr in instructions {
if instr.opcode != OpCode::Initsslot {
continue;
}
if let Some(Operand::U8(count)) = &instr.operand {
return Some(*count as usize);
}
}
None
}