FerroBabe 0.1.2

A lightweight Rust library for disassembling Java class files.
Documentation
use std::fmt;

use crate::model::{
    Class, ConstantPoolIndex, ConstantRef, Instruction, InstructionOperand, LdcValueRef,
    MemberReference,
};

use super::Formatter;
use super::opcode::mnemonic;

#[derive(Debug, Default, Clone, Copy)]
/// FerroBabe's compact formatter for bytecode-oriented inspection.
///
/// The formatter preserves instruction order and prints bytecode offsets, raw descriptors,
/// constant-pool references, branch destinations, and exception-handler ranges. It does not
/// reconstruct source-level control flow.
pub struct FerroFormatter;

impl Formatter for FerroFormatter {
    fn write_class(&self, class: &Class, output: &mut dyn fmt::Write) -> fmt::Result {
        write!(output, "class {}", class.name())?;
        if let Some(super_name) = class.super_name() {
            write!(output, " : {super_name}")?;
        }
        write!(output, " ")?;
        write_flags(output, class.access_flags(), FlagContext::Class)?;
        writeln!(
            output,
            " v{}.{}",
            class.version().major(),
            class.version().minor()
        )?;

        let interfaces: Vec<_> = class.interfaces().collect();
        if !interfaces.is_empty() {
            writeln!(output, "implements {}", interfaces.join(", "))?;
        }

        for field in class.fields() {
            write!(output, "\nfield {} {} ", field.name(), field.descriptor())?;
            write_flags(output, field.access_flags(), FlagContext::Field)?;
            writeln!(output)?;
        }

        for method in class.methods() {
            write!(output, "\nmethod {}{} ", method.name(), method.descriptor())?;
            write_flags(output, method.access_flags(), FlagContext::Method)?;

            if method.has_code() {
                writeln!(
                    output,
                    " stack={} locals={}",
                    method.max_stack(),
                    method.max_locals()
                )?;

                if let Some(instructions) = method.instructions() {
                    for instruction in instructions {
                        write_instruction(output, class, instruction)?;
                    }
                }

                for handler in method.exception_handlers() {
                    write!(
                        output,
                        "  catch {:04x}..{:04x} -> {:04x}",
                        handler.start().get(),
                        handler.end().get(),
                        handler.handler().get()
                    )?;
                    if let Some(catch_type) = handler.catch_type() {
                        write!(output, " {catch_type}")?;
                    }
                    writeln!(output)?;
                }
            } else {
                writeln!(output)?;
            }
        }

        Ok(())
    }
}

fn write_instruction(
    output: &mut dyn fmt::Write,
    class: &Class,
    instruction: Instruction<'_>,
) -> fmt::Result {
    write!(
        output,
        "  {:04x}  {:<16}",
        instruction.offset().get(),
        mnemonic(instruction.opcode())
    )?;
    write_operand(output, class, instruction.operand())?;
    writeln!(output)
}

fn write_operand(
    output: &mut dyn fmt::Write,
    class: &Class,
    operand: InstructionOperand<'_>,
) -> fmt::Result {
    match operand {
        InstructionOperand::None => Ok(()),
        InstructionOperand::Immediate(value) => write!(output, "{value}"),
        InstructionOperand::Local(index) => write!(output, "{index}"),
        InstructionOperand::ConstantPool(index) => write_constant(output, class, index),
        InstructionOperand::Member(reference) => write_member_reference(output, class, reference),
        InstructionOperand::InvokeInterface { method, count } => {
            write_constant(output, class, method)?;
            write!(output, " count={count}")
        }
        InstructionOperand::InvokeDynamic { call_site } => write_constant(output, class, call_site),
        InstructionOperand::Branch { target, .. } => write!(output, "{:04x}", target),
        InstructionOperand::Ldc(value) => write_ldc_value(output, class, value),
        InstructionOperand::Increment { local, amount } => write!(output, "{local} {amount}"),
        InstructionOperand::TableSwitch {
            default,
            low,
            high,
            targets,
            base_offset,
        } => {
            write!(output, "{low}..{high} default={:04x} [", default.target())?;
            for (index, target) in targets.iter().enumerate() {
                if index > 0 {
                    write!(output, ", ")?;
                }
                write!(output, "{:04x}", i32::from(base_offset.get()) + target)?;
            }
            write!(output, "]")
        }
        InstructionOperand::LookupSwitch {
            default,
            pairs,
            base_offset,
        } => {
            write!(output, "default={:04x} [", default.target())?;
            for (index, (key, target)) in pairs.iter().enumerate() {
                if index > 0 {
                    write!(output, ", ")?;
                }
                write!(
                    output,
                    "{key}:{:04x}",
                    i32::from(base_offset.get()) + target
                )?;
            }
            write!(output, "]")
        }
        InstructionOperand::MultiArray {
            class: index,
            dimensions,
        } => {
            write_constant(output, class, index)?;
            write!(output, " dims={dimensions}")
        }
    }
}

fn write_ldc_value(
    output: &mut dyn fmt::Write,
    class: &Class,
    value: LdcValueRef<'_>,
) -> fmt::Result {
    match value {
        LdcValueRef::ConstantPool(index) => write_constant(output, class, index),
        LdcValueRef::String(value) => write!(output, "{value:?}"),
        LdcValueRef::TypeDescriptor => write!(output, "<type>"),
        LdcValueRef::Integer(value) => write!(output, "{value}"),
        LdcValueRef::Float(value) => write!(output, "{value}"),
        LdcValueRef::Long(value) => write!(output, "{value}"),
        LdcValueRef::Double(value) => write!(output, "{value}"),
    }
}

fn write_member_reference(
    output: &mut dyn fmt::Write,
    class: &Class,
    reference: MemberReference<'_>,
) -> fmt::Result {
    match reference {
        MemberReference::ConstantPool(index) => write_constant(output, class, index),
        MemberReference::Symbolic {
            owner,
            name,
            descriptor,
        } => write!(output, "{owner}.{name}{descriptor}"),
    }
}

fn write_constant(
    output: &mut dyn fmt::Write,
    class: &Class,
    index: ConstantPoolIndex,
) -> fmt::Result {
    write!(output, "#{}", index.get())?;
    match class.constant(index) {
        Some(ConstantRef::Class { name }) => {
            if let Some(name) = utf8(class, name) {
                write!(output, " {name}")?;
            }
        }
        Some(ConstantRef::String { value }) => {
            if let Some(value) = utf8(class, value) {
                write!(output, " {value:?}")?;
            }
        }
        Some(ConstantRef::FieldReference {
            class: owner,
            name_and_type,
        })
        | Some(ConstantRef::MethodReference {
            class: owner,
            name_and_type,
        })
        | Some(ConstantRef::InterfaceMethodReference {
            class: owner,
            name_and_type,
        }) => write_member_constant(output, class, owner, name_and_type)?,
        Some(ConstantRef::Integer(value)) => write!(output, " {value}")?,
        Some(ConstantRef::Float(value)) => write!(output, " {value}")?,
        Some(ConstantRef::Long(value)) => write!(output, " {value}")?,
        Some(ConstantRef::Double(value)) => write!(output, " {value}")?,
        _ => {}
    }
    Ok(())
}

fn write_member_constant(
    output: &mut dyn fmt::Write,
    class: &Class,
    owner: ConstantPoolIndex,
    name_and_type_index: ConstantPoolIndex,
) -> fmt::Result {
    let Some(owner) = class_name(class, owner) else {
        return Ok(());
    };
    let Some((name, descriptor)) = name_and_type(class, name_and_type_index) else {
        return Ok(());
    };
    write!(output, " {owner}.{name}{descriptor}")
}

fn class_name(class: &Class, index: ConstantPoolIndex) -> Option<&str> {
    let ConstantRef::Class { name } = class.constant(index)? else {
        return None;
    };
    utf8(class, name)
}

fn name_and_type(class: &Class, index: ConstantPoolIndex) -> Option<(&str, &str)> {
    let ConstantRef::NameAndType { name, descriptor } = class.constant(index)? else {
        return None;
    };
    Some((utf8(class, name)?, utf8(class, descriptor)?))
}

fn utf8(class: &Class, index: ConstantPoolIndex) -> Option<&str> {
    let ConstantRef::Utf8(value) = class.constant(index)? else {
        return None;
    };
    Some(value)
}

#[derive(Clone, Copy)]
enum FlagContext {
    Class,
    Field,
    Method,
}

fn write_flags(output: &mut dyn fmt::Write, flags: u16, context: FlagContext) -> fmt::Result {
    write!(output, "[")?;
    let mut first = true;

    for (bit, name) in flag_names(context) {
        if flags & bit != 0 {
            if !first {
                write!(output, " ")?;
            }
            write!(output, "{name}")?;
            first = false;
        }
    }

    write!(output, "]")
}

fn flag_names(context: FlagContext) -> &'static [(u16, &'static str)] {
    const CLASS_FLAGS: &[(u16, &str)] = &[
        (0x0001, "public"),
        (0x0010, "final"),
        (0x0020, "super"),
        (0x0200, "interface"),
        (0x0400, "abstract"),
        (0x1000, "synthetic"),
        (0x2000, "annotation"),
        (0x4000, "enum"),
        (0x8000, "module"),
    ];
    const FIELD_FLAGS: &[(u16, &str)] = &[
        (0x0001, "public"),
        (0x0002, "private"),
        (0x0004, "protected"),
        (0x0008, "static"),
        (0x0010, "final"),
        (0x0040, "volatile"),
        (0x0080, "transient"),
        (0x1000, "synthetic"),
        (0x4000, "enum"),
    ];
    const METHOD_FLAGS: &[(u16, &str)] = &[
        (0x0001, "public"),
        (0x0002, "private"),
        (0x0004, "protected"),
        (0x0008, "static"),
        (0x0010, "final"),
        (0x0020, "synchronized"),
        (0x0040, "bridge"),
        (0x0080, "varargs"),
        (0x0100, "native"),
        (0x0400, "abstract"),
        (0x0800, "strict"),
        (0x1000, "synthetic"),
    ];

    match context {
        FlagContext::Class => CLASS_FLAGS,
        FlagContext::Field => FIELD_FLAGS,
        FlagContext::Method => METHOD_FLAGS,
    }
}