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
import { joinType } from "./type-support.js";

export function handleSlotInstruction(ins, stack, locals, args, statics, aliases, pointerTargets, unk) {
  const m = ins.opcode.mnemonic;

  if (m === "INITSSLOT") {
    if (ins.operand?.kind === "U8" && ins.operand.value > 0) {
      ensure(statics, ins.operand.value - 1);
      ensureAliases(aliases.statics, ins.operand.value - 1);
      ensurePointerTargets(pointerTargets.statics, ins.operand.value - 1);
    }
    return true;
  }

  if (m === "INITSLOT") {
    const operand = ins.operand;
    if (operand?.kind === "Bytes" && operand.value.length >= 2) {
      if (operand.value[0] > 0) {
        ensure(locals, operand.value[0] - 1);
        ensureAliases(aliases.locals, operand.value[0] - 1);
        ensurePointerTargets(pointerTargets.locals, operand.value[0] - 1);
      }
      if (operand.value[1] > 0) {
        ensure(args, operand.value[1] - 1);
        ensureAliases(aliases.args, operand.value[1] - 1);
        ensurePointerTargets(pointerTargets.args, operand.value[1] - 1);
      }
    }
    return true;
  }

  const slotMatch = /^(LD|ST)(LOC|ARG|SFLD)(\d*)$/u.exec(m);
  if (!slotMatch) return false;

  const slots = slotMatch[2] === "LOC" ? locals : slotMatch[2] === "ARG" ? args : statics;
  const aliasSlots = slotMatch[2] === "LOC" ? aliases.locals : slotMatch[2] === "ARG" ? aliases.args : aliases.statics;
  const pointerTargetSlots =
    slotMatch[2] === "LOC"
      ? pointerTargets.locals
      : slotMatch[2] === "ARG"
        ? pointerTargets.args
        : pointerTargets.statics;
  const kind = slotMatch[2] === "LOC" ? "local" : slotMatch[2] === "ARG" ? "argument" : "static";
  const index = slotMatch[3] !== "" ? Number(slotMatch[3]) : ins.operand?.kind === "U8" ? ins.operand.value : null;

  if (slotMatch[1] === "LD") {
    loadSlot(stack, slots, aliasSlots, pointerTargetSlots, kind, index, unk);
  } else {
    storeSlot(stack, slots, aliasSlots, pointerTargetSlots, kind, index, unk);
  }
  return true;
}

function loadSlot(stack, slots, aliasSlots, pointerTargetSlots, kind, index, unk) {
  if (index === null) {
    stack.push(unk());
    return;
  }

  ensure(slots, index);
  ensureAliases(aliasSlots, index);
  ensurePointerTargets(pointerTargetSlots, index);
  stack.push({
    ty: slots[index],
    lit: null,
    ptr: pointerTargetSlots[index] ?? null,
    origins: slotOrigins(aliasSlots, index, { kind, index }),
  });
}

function storeSlot(stack, slots, aliasSlots, pointerTargetSlots, kind, index, unk) {
  const value = stack.pop() ?? unk();
  if (index === null) {
    return;
  }

  ensure(slots, index);
  ensureAliases(aliasSlots, index);
  ensurePointerTargets(pointerTargetSlots, index);
  slots[index] = joinType(slots[index], value.ty);
  pointerTargetSlots[index] = value.ptr ?? null;
  aliasSlots[index] = [];

  const destination = { kind, index };
  for (const origin of value.origins ?? []) {
    if (!sameOrigin(origin, destination)) pushUniqueOrigin(aliasSlots[index], origin);
  }
}

function ensure(slots, index) {
  while (slots.length <= index) slots.push("unknown");
}

function ensureAliases(aliasSlots, index) {
  while (aliasSlots.length <= index) aliasSlots.push([]);
}

function ensurePointerTargets(targetSlots, index) {
  while (targetSlots.length <= index) targetSlots.push(null);
}

function sameOrigin(a, b) {
  return a.kind === b.kind && a.index === b.index;
}

function pushUniqueOrigin(origins, origin) {
  if (!origins.some((existing) => sameOrigin(existing, origin))) origins.push(origin);
}

function slotOrigins(aliasSlots, index, origin) {
  const origins = [];
  pushUniqueOrigin(origins, origin);
  for (const alias of aliasSlots[index] ?? []) pushUniqueOrigin(origins, alias);
  return origins;
}