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 std::collections::HashSet;

use super::super::super::HighLevelEmitter;
use super::scan::next_if_line;

impl HighLevelEmitter {
    /// Converts `label_X: <setup> if COND { <body> goto label_X; <phi> }` into
    /// `<setup> while COND { <body> <phi> <setup> }` — recovering while-loop
    /// semantics from backward unconditional JMPs inside if-blocks.
    pub(crate) fn rewrite_if_goto_to_while(statements: &mut Vec<String>) {
        // The pass can only succeed when a matching `goto label_X;` exists
        // (checked at lines below inside the if-block). Pre-collect those once
        // so labels with no corresponding goto are skipped in O(1) instead of
        // each running an unbounded forward `next_if_line` scan to the tail of
        // the vector. Without this guard the pass is O(labels x N): a crafted
        // in-cap NEF that emits many `label_X:` lines with no following `if`
        // drives it quadratic — a decompiler-hang DoS.
        let goto_targets: HashSet<String> = statements
            .iter()
            .map(|s| s.trim())
            .filter(|t| t.starts_with("goto label_") && t.ends_with(';'))
            .map(str::to_string)
            .collect();
        if goto_targets.is_empty() {
            return;
        }

        let mut index = 0;
        while index < statements.len() {
            let trimmed = statements[index].trim().to_string();

            // Match: label_0xXXXX:
            let Some(label) = trimmed.strip_suffix(':') else {
                index += 1;
                continue;
            };
            if !label.starts_with("label_") {
                index += 1;
                continue;
            }

            // Fast-skip: with no `goto label_X;` anywhere, the search below can
            // only fail, so skip the unbounded `next_if_line` scan entirely.
            if !goto_targets.contains(format!("goto {label};").as_str()) {
                index += 1;
                continue;
            }

            // Find next `if ... {` after the label
            let Some(if_idx) = next_if_line(statements, index) else {
                index += 1;
                continue;
            };

            // Find the matching `}`
            let Some(end_idx) = Self::find_block_end(statements, if_idx) else {
                index += 1;
                continue;
            };
            if statements[end_idx].trim() != "}" {
                index += 1;
                continue;
            }

            // Find `goto label_X;` inside the if-block
            let goto_target = format!("goto {label};");
            let Some(goto_idx) =
                (if_idx + 1..end_idx).find(|&i| statements[i].trim() == goto_target)
            else {
                index += 1;
                continue;
            };

            // Collect setup lines (non-empty, non-comment) between label and if
            let setup_lines: Vec<String> = (index + 1..if_idx)
                .filter(|&i| {
                    let t = statements[i].trim();
                    !t.is_empty() && !t.starts_with("//")
                })
                .map(|i| statements[i].clone())
                .collect();

            // Transform: remove label, change if->while, remove goto,
            // append setup copies at end of loop body
            statements[index].clear(); // remove label
            let if_line = statements[if_idx].trim().to_string();
            statements[if_idx] = if_line.replacen("if ", "while ", 1);
            statements[goto_idx].clear(); // remove goto

            // Insert setup copies before closing `}`
            if !setup_lines.is_empty() {
                for (j, line) in setup_lines.into_iter().enumerate() {
                    statements.insert(end_idx + j, line);
                }
            }

            index += 1;
        }
    }
}