Skip to main content

ferro_babe/format/
ferro.rs

1use std::fmt;
2
3use crate::model::{
4    Class, ConstantPoolIndex, ConstantRef, Instruction, InstructionOperand, LdcValueRef,
5    MemberReference,
6};
7
8use super::Formatter;
9use super::opcode::mnemonic;
10
11#[derive(Debug, Default, Clone, Copy)]
12/// FerroBabe's compact formatter for bytecode-oriented inspection.
13///
14/// The formatter preserves instruction order and prints bytecode offsets, raw descriptors,
15/// constant-pool references, branch destinations, and exception-handler ranges. It does not
16/// reconstruct source-level control flow.
17pub struct FerroFormatter;
18
19impl Formatter for FerroFormatter {
20    fn write_class(&self, class: &Class, output: &mut dyn fmt::Write) -> fmt::Result {
21        write!(output, "class {}", class.name())?;
22        if let Some(super_name) = class.super_name() {
23            write!(output, " : {super_name}")?;
24        }
25        write!(output, " ")?;
26        write_flags(output, class.access_flags(), FlagContext::Class)?;
27        writeln!(
28            output,
29            " v{}.{}",
30            class.version().major(),
31            class.version().minor()
32        )?;
33
34        let interfaces: Vec<_> = class.interfaces().collect();
35        if !interfaces.is_empty() {
36            writeln!(output, "implements {}", interfaces.join(", "))?;
37        }
38
39        for field in class.fields() {
40            write!(output, "\nfield {} {} ", field.name(), field.descriptor())?;
41            write_flags(output, field.access_flags(), FlagContext::Field)?;
42            writeln!(output)?;
43        }
44
45        for method in class.methods() {
46            write!(output, "\nmethod {}{} ", method.name(), method.descriptor())?;
47            write_flags(output, method.access_flags(), FlagContext::Method)?;
48
49            if method.has_code() {
50                writeln!(
51                    output,
52                    " stack={} locals={}",
53                    method.max_stack(),
54                    method.max_locals()
55                )?;
56
57                if let Some(instructions) = method.instructions() {
58                    for instruction in instructions {
59                        write_instruction(output, class, instruction)?;
60                    }
61                }
62
63                for handler in method.exception_handlers() {
64                    write!(
65                        output,
66                        "  catch {:04x}..{:04x} -> {:04x}",
67                        handler.start().get(),
68                        handler.end().get(),
69                        handler.handler().get()
70                    )?;
71                    if let Some(catch_type) = handler.catch_type() {
72                        write!(output, " {catch_type}")?;
73                    }
74                    writeln!(output)?;
75                }
76            } else {
77                writeln!(output)?;
78            }
79        }
80
81        Ok(())
82    }
83}
84
85fn write_instruction(
86    output: &mut dyn fmt::Write,
87    class: &Class,
88    instruction: Instruction<'_>,
89) -> fmt::Result {
90    write!(
91        output,
92        "  {:04x}  {:<16}",
93        instruction.offset().get(),
94        mnemonic(instruction.opcode())
95    )?;
96    write_operand(output, class, instruction.operand())?;
97    writeln!(output)
98}
99
100fn write_operand(
101    output: &mut dyn fmt::Write,
102    class: &Class,
103    operand: InstructionOperand<'_>,
104) -> fmt::Result {
105    match operand {
106        InstructionOperand::None => Ok(()),
107        InstructionOperand::Immediate(value) => write!(output, "{value}"),
108        InstructionOperand::Local(index) => write!(output, "{index}"),
109        InstructionOperand::ConstantPool(index) => write_constant(output, class, index),
110        InstructionOperand::Member(reference) => write_member_reference(output, class, reference),
111        InstructionOperand::InvokeInterface { method, count } => {
112            write_constant(output, class, method)?;
113            write!(output, " count={count}")
114        }
115        InstructionOperand::InvokeDynamic { call_site } => write_constant(output, class, call_site),
116        InstructionOperand::Branch { target, .. } => write!(output, "{:04x}", target),
117        InstructionOperand::Ldc(value) => write_ldc_value(output, class, value),
118        InstructionOperand::Increment { local, amount } => write!(output, "{local} {amount}"),
119        InstructionOperand::TableSwitch {
120            default,
121            low,
122            high,
123            targets,
124            base_offset,
125        } => {
126            write!(output, "{low}..{high} default={:04x} [", default.target())?;
127            for (index, target) in targets.iter().enumerate() {
128                if index > 0 {
129                    write!(output, ", ")?;
130                }
131                write!(output, "{:04x}", i32::from(base_offset.get()) + target)?;
132            }
133            write!(output, "]")
134        }
135        InstructionOperand::LookupSwitch {
136            default,
137            pairs,
138            base_offset,
139        } => {
140            write!(output, "default={:04x} [", default.target())?;
141            for (index, (key, target)) in pairs.iter().enumerate() {
142                if index > 0 {
143                    write!(output, ", ")?;
144                }
145                write!(
146                    output,
147                    "{key}:{:04x}",
148                    i32::from(base_offset.get()) + target
149                )?;
150            }
151            write!(output, "]")
152        }
153        InstructionOperand::MultiArray {
154            class: index,
155            dimensions,
156        } => {
157            write_constant(output, class, index)?;
158            write!(output, " dims={dimensions}")
159        }
160    }
161}
162
163fn write_ldc_value(
164    output: &mut dyn fmt::Write,
165    class: &Class,
166    value: LdcValueRef<'_>,
167) -> fmt::Result {
168    match value {
169        LdcValueRef::ConstantPool(index) => write_constant(output, class, index),
170        LdcValueRef::String(value) => write!(output, "{value:?}"),
171        LdcValueRef::TypeDescriptor => write!(output, "<type>"),
172        LdcValueRef::Integer(value) => write!(output, "{value}"),
173        LdcValueRef::Float(value) => write!(output, "{value}"),
174        LdcValueRef::Long(value) => write!(output, "{value}"),
175        LdcValueRef::Double(value) => write!(output, "{value}"),
176    }
177}
178
179fn write_member_reference(
180    output: &mut dyn fmt::Write,
181    class: &Class,
182    reference: MemberReference<'_>,
183) -> fmt::Result {
184    match reference {
185        MemberReference::ConstantPool(index) => write_constant(output, class, index),
186        MemberReference::Symbolic {
187            owner,
188            name,
189            descriptor,
190        } => write!(output, "{owner}.{name}{descriptor}"),
191    }
192}
193
194fn write_constant(
195    output: &mut dyn fmt::Write,
196    class: &Class,
197    index: ConstantPoolIndex,
198) -> fmt::Result {
199    write!(output, "#{}", index.get())?;
200    match class.constant(index) {
201        Some(ConstantRef::Class { name }) => {
202            if let Some(name) = utf8(class, name) {
203                write!(output, " {name}")?;
204            }
205        }
206        Some(ConstantRef::String { value }) => {
207            if let Some(value) = utf8(class, value) {
208                write!(output, " {value:?}")?;
209            }
210        }
211        Some(ConstantRef::FieldReference {
212            class: owner,
213            name_and_type,
214        })
215        | Some(ConstantRef::MethodReference {
216            class: owner,
217            name_and_type,
218        })
219        | Some(ConstantRef::InterfaceMethodReference {
220            class: owner,
221            name_and_type,
222        }) => write_member_constant(output, class, owner, name_and_type)?,
223        Some(ConstantRef::Integer(value)) => write!(output, " {value}")?,
224        Some(ConstantRef::Float(value)) => write!(output, " {value}")?,
225        Some(ConstantRef::Long(value)) => write!(output, " {value}")?,
226        Some(ConstantRef::Double(value)) => write!(output, " {value}")?,
227        _ => {}
228    }
229    Ok(())
230}
231
232fn write_member_constant(
233    output: &mut dyn fmt::Write,
234    class: &Class,
235    owner: ConstantPoolIndex,
236    name_and_type_index: ConstantPoolIndex,
237) -> fmt::Result {
238    let Some(owner) = class_name(class, owner) else {
239        return Ok(());
240    };
241    let Some((name, descriptor)) = name_and_type(class, name_and_type_index) else {
242        return Ok(());
243    };
244    write!(output, " {owner}.{name}{descriptor}")
245}
246
247fn class_name(class: &Class, index: ConstantPoolIndex) -> Option<&str> {
248    let ConstantRef::Class { name } = class.constant(index)? else {
249        return None;
250    };
251    utf8(class, name)
252}
253
254fn name_and_type(class: &Class, index: ConstantPoolIndex) -> Option<(&str, &str)> {
255    let ConstantRef::NameAndType { name, descriptor } = class.constant(index)? else {
256        return None;
257    };
258    Some((utf8(class, name)?, utf8(class, descriptor)?))
259}
260
261fn utf8(class: &Class, index: ConstantPoolIndex) -> Option<&str> {
262    let ConstantRef::Utf8(value) = class.constant(index)? else {
263        return None;
264    };
265    Some(value)
266}
267
268#[derive(Clone, Copy)]
269enum FlagContext {
270    Class,
271    Field,
272    Method,
273}
274
275fn write_flags(output: &mut dyn fmt::Write, flags: u16, context: FlagContext) -> fmt::Result {
276    write!(output, "[")?;
277    let mut first = true;
278
279    for (bit, name) in flag_names(context) {
280        if flags & bit != 0 {
281            if !first {
282                write!(output, " ")?;
283            }
284            write!(output, "{name}")?;
285            first = false;
286        }
287    }
288
289    write!(output, "]")
290}
291
292fn flag_names(context: FlagContext) -> &'static [(u16, &'static str)] {
293    const CLASS_FLAGS: &[(u16, &str)] = &[
294        (0x0001, "public"),
295        (0x0010, "final"),
296        (0x0020, "super"),
297        (0x0200, "interface"),
298        (0x0400, "abstract"),
299        (0x1000, "synthetic"),
300        (0x2000, "annotation"),
301        (0x4000, "enum"),
302        (0x8000, "module"),
303    ];
304    const FIELD_FLAGS: &[(u16, &str)] = &[
305        (0x0001, "public"),
306        (0x0002, "private"),
307        (0x0004, "protected"),
308        (0x0008, "static"),
309        (0x0010, "final"),
310        (0x0040, "volatile"),
311        (0x0080, "transient"),
312        (0x1000, "synthetic"),
313        (0x4000, "enum"),
314    ];
315    const METHOD_FLAGS: &[(u16, &str)] = &[
316        (0x0001, "public"),
317        (0x0002, "private"),
318        (0x0004, "protected"),
319        (0x0008, "static"),
320        (0x0010, "final"),
321        (0x0020, "synchronized"),
322        (0x0040, "bridge"),
323        (0x0080, "varargs"),
324        (0x0100, "native"),
325        (0x0400, "abstract"),
326        (0x0800, "strict"),
327        (0x1000, "synthetic"),
328    ];
329
330    match context {
331        FlagContext::Class => CLASS_FLAGS,
332        FlagContext::Field => FIELD_FLAGS,
333        FlagContext::Method => METHOD_FLAGS,
334    }
335}