neo-decompiler 0.6.2

Minimal tooling for inspecting Neo N3 NEF bytecode
Documentation
import { scanSlotCounts, scanStaticSlotCount } from "./util.js";

export function buildXrefs(instructions, methodGroups) {
  const staticCount = scanStaticSlotCount(instructions);
  return {
    methods: methodGroups.map((group) => {
      const slice = group.instructions;
      const [localCount, argCount] = scanSlotCounts(slice);
      const locals = Array.from({ length: localCount }, (_, index) => ({
        index,
        reads: [],
        writes: [],
      }));
      const argumentsXrefs = Array.from({ length: argCount }, (_, index) => ({
        index,
        reads: [],
        writes: [],
      }));
      const statics = Array.from({ length: staticCount }, (_, index) => ({
        index,
        reads: [],
        writes: [],
      }));

      for (const instruction of slice) {
        const access = slotAccess(instruction);
        if (!access) continue;
        const target =
          access.kind === "local"
            ? locals
            : access.kind === "argument"
              ? argumentsXrefs
              : statics;
        while (target.length <= access.index) {
          target.push({ index: target.length, reads: [], writes: [] });
        }
        if (access.isWrite) {
          target[access.index].writes.push(instruction.offset);
        } else {
          target[access.index].reads.push(instruction.offset);
        }
      }

      return {
        method: { offset: group.start, name: group.name },
        locals,
        arguments: argumentsXrefs,
        statics,
      };
    }),
  };
}

function slotAccess(instruction) {
  const mnemonic = instruction.opcode.mnemonic;
  if (/^LDLOC\d+$/u.test(mnemonic)) {
    return { kind: "local", index: Number(mnemonic.slice("LDLOC".length)), isWrite: false };
  }
  if (mnemonic === "LDLOC" && instruction.operand?.kind === "U8") {
    return { kind: "local", index: instruction.operand.value, isWrite: false };
  }
  if (/^STLOC\d+$/u.test(mnemonic)) {
    return { kind: "local", index: Number(mnemonic.slice("STLOC".length)), isWrite: true };
  }
  if (mnemonic === "STLOC" && instruction.operand?.kind === "U8") {
    return { kind: "local", index: instruction.operand.value, isWrite: true };
  }

  if (/^LDARG\d+$/u.test(mnemonic)) {
    return { kind: "argument", index: Number(mnemonic.slice("LDARG".length)), isWrite: false };
  }
  if (mnemonic === "LDARG" && instruction.operand?.kind === "U8") {
    return { kind: "argument", index: instruction.operand.value, isWrite: false };
  }
  if (/^STARG\d+$/u.test(mnemonic)) {
    return { kind: "argument", index: Number(mnemonic.slice("STARG".length)), isWrite: true };
  }
  if (mnemonic === "STARG" && instruction.operand?.kind === "U8") {
    return { kind: "argument", index: instruction.operand.value, isWrite: true };
  }

  if (/^LDSFLD\d+$/u.test(mnemonic)) {
    return { kind: "static", index: Number(mnemonic.slice("LDSFLD".length)), isWrite: false };
  }
  if (mnemonic === "LDSFLD" && instruction.operand?.kind === "U8") {
    return { kind: "static", index: instruction.operand.value, isWrite: false };
  }
  if (/^STSFLD\d+$/u.test(mnemonic)) {
    return { kind: "static", index: Number(mnemonic.slice("STSFLD".length)), isWrite: true };
  }
  if (mnemonic === "STSFLD" && instruction.operand?.kind === "U8") {
    return { kind: "static", index: instruction.operand.value, isWrite: true };
  }
  return null;
}