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 {
  collectDerivedWarnings,
  rewriteForLoops,
} from "./high-level-control-flow-shared.js";
import { findMnemonicFrom, findTryFrom, tryHandlerTargets } from "./high-level-try-shared.js";

export function createMalformedTryLifter(runtime) {
  const { createState, cloneState, executeStraightLine } = runtime;

  function liftMalformedTryBlock({
    instructions,
    manifestMethod,
    context,
    methodOffset,
    tryIndex,
    bodyStartIndex,
    catchIndex,
    finallyIndex,
    initialState = null,
  }) {
    const handlerIndices = [catchIndex, finallyIndex]
      .filter((index) => index !== null && index !== undefined)
      .sort((left, right) => left - right);
    const bodyEndIndex = handlerIndices[0] ?? instructions.length;
    if (bodyEndIndex < bodyStartIndex) {
      return null;
    }

    const prefixState =
      initialState === null
        ? createState(manifestMethod, context, methodOffset, instructions)
        : cloneState(initialState);
    const statementBase = initialState === null ? 0 : prefixState.statements.length;
    if (initialState === null) {
      executeStraightLine(prefixState, instructions.slice(0, tryIndex));
    }

    const statements = [...prefixState.statements.slice(statementBase), "try {"];
    const branchStates = [];
    const tryBodyState = cloneState(prefixState);
    const bodySlice = instructions.slice(bodyStartIndex, bodyEndIndex);
    const bodyResult = liftPossiblyNestedSlice(
      tryBodyState,
      bodySlice,
      manifestMethod,
      context,
      methodOffset,
    );
    statements.push(...bodyResult.statements);
    branchStates.push(bodyResult.state);

    let catchState = null;
    if (catchIndex !== null && catchIndex !== undefined) {
      const catchEndIndex = finallyIndex ?? instructions.length;
      catchState = cloneState(prefixState);
      catchState.stack.push("exception");
      executeStraightLine(catchState, instructions.slice(catchIndex, catchEndIndex));
      statements.push("} catch {");
      statements.push(...catchState.statements.slice(prefixState.statements.length));
      branchStates.push(catchState);
    }

    let finallyState = null;
    if (finallyIndex !== null && finallyIndex !== undefined) {
      const finallyEndIndex = findMnemonicFrom(instructions, finallyIndex, "ENDFINALLY");
      const finallySliceEnd = finallyEndIndex >= 0 ? finallyEndIndex : instructions.length;
      const upstream = branchStates.at(-1) ?? tryBodyState;
      finallyState = cloneState(upstream);
      executeStraightLine(finallyState, instructions.slice(finallyIndex, finallySliceEnd));
      statements.push("} finally {");
      statements.push(...finallyState.statements.slice(upstream.statements.length));
      branchStates.push(finallyState);
    }

    statements.push("}");

    const endState = branchStates.at(-1) ?? tryBodyState;
    const result = rewriteForLoops({
      statements,
      warnings: collectDerivedWarnings(prefixState, ...branchStates),
    });
    result.state = endState;
    return result;
  }

  function liftPossiblyNestedSlice(state, slice, manifestMethod, context, methodOffset) {
    const nestedResult = liftNestedMalformedTrySlice(
      state,
      slice,
      manifestMethod,
      context,
      methodOffset,
    );
    if (nestedResult !== null) {
      return nestedResult;
    }

    const statementBase = state.statements.length;
    executeStraightLine(state, slice);
    return {
      statements: state.statements.slice(statementBase),
      warnings: state.warnings,
      state,
    };
  }

  function liftNestedMalformedTrySlice(state, slice, manifestMethod, context, methodOffset) {
    const nestedTryIndex = findTryFrom(slice, 0);
    if (nestedTryIndex < 0) {
      return null;
    }

    const statementBase = state.statements.length;
    executeStraightLine(state, slice.slice(0, nestedTryIndex));

    const nestedInstruction = slice[nestedTryIndex];
    const handlerTargets = tryHandlerTargets(nestedInstruction);
    if (handlerTargets === null) {
      return null;
    }

    const indexByOffset = new Map();
    for (let i = 0; i < slice.length; i++) {
      indexByOffset.set(slice[i].offset, i);
    }

    const { bodyStart, catchTarget, finallyTarget } = handlerTargets;
    const bodyStartIndex = indexByOffset.get(bodyStart);
    if (bodyStartIndex === undefined) {
      return null;
    }

    const catchIndex = catchTarget === null ? null : indexByOffset.get(catchTarget);
    const finallyIndex = finallyTarget === null ? null : indexByOffset.get(finallyTarget);
    const hasMissingHandlers =
      (catchTarget !== null && catchIndex === undefined) ||
      (finallyTarget !== null && finallyIndex === undefined);
    const hasNoUsableHandlers = catchTarget === null && finallyTarget === null;
    const endtryIndex = findMnemonicFrom(slice, bodyStartIndex, "ENDTRY");
    if (!hasNoUsableHandlers && !hasMissingHandlers && endtryIndex >= 0) {
      return null;
    }

    const nestedResult = liftMalformedTryBlock({
      instructions: slice,
      manifestMethod,
      context,
      methodOffset,
      tryIndex: nestedTryIndex,
      bodyStartIndex,
      catchIndex,
      finallyIndex,
      initialState: state,
    });
    if (nestedResult === null) {
      return null;
    }

    return {
      statements: [
        ...state.statements.slice(statementBase),
        ...nestedResult.statements,
      ],
      warnings: nestedResult.warnings ?? [],
      state: nestedResult.state ?? state,
    };
  }

  return liftMalformedTryBlock;
}