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
use crate::instruction::OpCode;

use super::super::basic_block::Terminator;
use super::CfgBuilder;

impl<'a> CfgBuilder<'a> {
    pub(super) fn compute_terminator(
        &self,
        _start_index: usize,
        end_index: usize,
        leaders: &[usize],
    ) -> Terminator {
        if end_index == 0 || end_index > self.instructions.len() {
            return Terminator::Unknown;
        }

        let last_instr = &self.instructions[end_index - 1];

        match last_instr.opcode {
            OpCode::Ret => Terminator::Return,
            OpCode::Throw => Terminator::Throw,
            OpCode::Abort | OpCode::Abortmsg => Terminator::Abort,
            OpCode::Endfinally => Terminator::Unknown,

            OpCode::Jmp | OpCode::Jmp_L => {
                if let Some(target) = self.jump_target(end_index - 1, last_instr) {
                    let target_block = self.offset_to_block_id(target, leaders);
                    Terminator::Jump {
                        target: target_block,
                    }
                } else {
                    Terminator::Unknown
                }
            }

            OpCode::Endtry | OpCode::EndtryL => {
                if let Some(target) = self.jump_target(end_index - 1, last_instr) {
                    let continuation = self.offset_to_block_id(target, leaders);
                    Terminator::EndTry { continuation }
                } else {
                    Terminator::Unknown
                }
            }

            // `Jmpifnot` jumps when its boolean operand is FALSE. The
            // `Branch` terminator's contract is `then_target` = condition-TRUE
            // branch (see `Terminator::Branch`), so for `Jmpifnot` the
            // fallthrough (next instruction) is `then_target` and the jump
            // target is `else_target`. All other conditional jumps below jump
            // when their condition is true, so for them the jump target *is*
            // the condition-true (`then`) branch.
            OpCode::Jmpifnot | OpCode::Jmpifnot_L => {
                if let Some(target) = self.jump_target(end_index - 1, last_instr) {
                    let else_block = self.offset_to_block_id(target, leaders);
                    let then_block = self
                        .instructions
                        .get(end_index)
                        .map(|ins| self.offset_to_block_id(ins.offset, leaders))
                        .unwrap_or_else(|| self.offset_to_block_id(self.end_offset(), leaders));
                    Terminator::Branch {
                        then_target: then_block,
                        else_target: else_block,
                    }
                } else {
                    Terminator::Unknown
                }
            }
            OpCode::Jmpif
            | OpCode::Jmpif_L
            | OpCode::JmpEq
            | OpCode::JmpEq_L
            | OpCode::JmpNe
            | OpCode::JmpNe_L
            | OpCode::JmpGt
            | OpCode::JmpGt_L
            | OpCode::JmpGe
            | OpCode::JmpGe_L
            | OpCode::JmpLt
            | OpCode::JmpLt_L
            | OpCode::JmpLe
            | OpCode::JmpLe_L => {
                if let Some(target) = self.jump_target(end_index - 1, last_instr) {
                    let then_block = self.offset_to_block_id(target, leaders);
                    let else_block = self
                        .instructions
                        .get(end_index)
                        .map(|ins| self.offset_to_block_id(ins.offset, leaders))
                        .unwrap_or_else(|| self.offset_to_block_id(self.end_offset(), leaders));
                    Terminator::Branch {
                        then_target: then_block,
                        else_target: else_block,
                    }
                } else {
                    Terminator::Unknown
                }
            }

            OpCode::Try | OpCode::TryL => {
                let body_offset = self
                    .instruction_end_offset(end_index - 1)
                    .unwrap_or_else(|| self.end_offset());
                let body_target = self.offset_to_block_id(body_offset, leaders);

                let (catch_target, finally_target) = self
                    .try_targets(end_index - 1, last_instr)
                    .unwrap_or((None, None));
                let catch_target = catch_target.map(|off| self.offset_to_block_id(off, leaders));
                let finally_target =
                    finally_target.map(|off| self.offset_to_block_id(off, leaders));

                Terminator::TryEntry {
                    body_target,
                    catch_target,
                    finally_target,
                }
            }

            _ => {
                if let Some(next) = self.instructions.get(end_index) {
                    let target_block = self.offset_to_block_id(next.offset, leaders);
                    Terminator::Fallthrough {
                        target: target_block,
                    }
                } else {
                    Terminator::Unknown
                }
            }
        }
    }
}