neo-decompiler 0.10.2

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 super::super::super::HighLevelEmitter;
use super::super::util::{is_else_open, is_if_open};
use super::parse::{is_temp, parse_label_line};

pub(super) fn extract_block_body(
    statements: &[String],
    header_index: usize,
) -> Option<(Vec<String>, usize)> {
    let end = HighLevelEmitter::find_block_end(statements, header_index)?;
    let body = statements
        .get(header_index + 1..end)
        .unwrap_or_default()
        .to_vec();
    Some((body, end))
}

pub(super) fn collect_trivia(statements: &[String], mut index: usize) -> (Vec<String>, usize) {
    let mut trivia = Vec::new();
    while index < statements.len() {
        let trimmed = statements[index].trim();
        if trimmed.is_empty() || trimmed.starts_with("//") {
            trivia.push(statements[index].clone());
            index += 1;
            continue;
        }
        break;
    }
    (trivia, index)
}

pub(super) fn find_first_if_in_range(
    statements: &[String],
    start: usize,
    end: usize,
) -> Option<usize> {
    let mut index = start;
    while index < end {
        let trimmed = statements[index].trim();
        if trimmed.is_empty() || trimmed.starts_with("//") {
            index += 1;
            continue;
        }
        if is_if_open(trimmed) {
            return Some(index);
        }
        index += 1;
    }
    None
}

pub(super) fn find_next_if_after_case_prelude(
    statements: &[String],
    start: usize,
) -> Option<usize> {
    let mut index = start;
    while index < statements.len() {
        let trimmed = statements[index].trim();
        if trimmed.is_empty() || trimmed.starts_with("//") {
            index += 1;
            continue;
        }
        if is_if_open(trimmed) {
            return Some(index);
        }
        if let Some(assign) = HighLevelEmitter::parse_assignment(statements[index].as_str()) {
            if is_temp(&assign.lhs) && temp_consumed_by_next_code(statements, index, &assign.lhs) {
                index += 1;
                continue;
            }
        }
        return None;
    }
    None
}

pub(super) fn temp_consumed_by_next_code(statements: &[String], index: usize, temp: &str) -> bool {
    statements
        .iter()
        .skip(index + 1)
        .find(|stmt| {
            let trimmed = stmt.trim();
            !trimmed.is_empty() && !trimmed.starts_with("//")
        })
        .is_some_and(|stmt| HighLevelEmitter::contains_identifier(stmt, temp))
}

pub(super) fn case_body_is_switch_safe(body: &[String], scrutinee: &str) -> bool {
    if body_ends_with_terminator(body) {
        return true;
    }
    !body.iter().any(|line| statement_reassigns(line, scrutinee))
}

pub(super) fn find_label_after(statements: &[String], start: usize, label: &str) -> Option<usize> {
    let needle = format!("{label}:");
    let mut index = start;
    while index < statements.len() {
        if statements[index].trim() == needle {
            return Some(index);
        }
        index += 1;
    }
    None
}

pub(super) fn find_label_in_range(
    statements: &[String],
    start: usize,
    end: usize,
    label: &str,
) -> Option<usize> {
    let needle = format!("{label}:");
    let mut index = start;
    while index < end {
        if statements[index].trim() == needle {
            return Some(index);
        }
        index += 1;
    }
    None
}

pub(super) fn find_else_block_after(statements: &[String], start: usize) -> Option<(usize, usize)> {
    let (_, header) = collect_trivia(statements, start);
    if header >= statements.len() || !is_else_open(statements[header].trim()) {
        return None;
    }
    let end = HighLevelEmitter::find_block_end(statements, header)?;
    Some((header, end))
}

pub(super) fn find_label_body_end(statements: &[String], start: usize) -> usize {
    let mut index = start;
    while index < statements.len() {
        if index > start && parse_label_line(statements[index].as_str()).is_some() {
            break;
        }
        index += 1;
    }
    index.saturating_sub(1)
}

pub(super) fn end_of_if_chain(statements: &[String], start: usize) -> Option<usize> {
    let if_end = HighLevelEmitter::find_block_end(statements, start)?;
    let (_, next) = collect_trivia(statements, if_end + 1);
    if next < statements.len() && is_else_open(statements[next].trim()) {
        return HighLevelEmitter::find_block_end(statements, next);
    }
    Some(if_end)
}

fn body_ends_with_terminator(body: &[String]) -> bool {
    for line in body.iter().rev() {
        let trimmed = line.trim();
        if trimmed.is_empty() || trimmed.starts_with("//") || trimmed == "{" || trimmed == "}" {
            continue;
        }
        return is_terminator_statement(trimmed);
    }
    false
}

fn is_terminator_statement(line: &str) -> bool {
    let trimmed = line.trim();
    trimmed == "return;"
        || trimmed.starts_with("return ")
        || trimmed.starts_with("throw")
        || trimmed.starts_with("abort")
        || trimmed.starts_with("goto ")
        || trimmed == "break;"
        || trimmed == "continue;"
}

fn statement_reassigns(line: &str, scrutinee: &str) -> bool {
    HighLevelEmitter::parse_assignment(line).is_some_and(|assignment| assignment.lhs == scrutinee)
}