Skip to main content

rust_asm/
class_writer.rs

1use std::collections::HashMap;
2
3use crate::class_reader::{
4    AttributeInfo, BootstrapMethod, CodeAttribute, CpInfo, ExceptionTableEntry, InnerClass,
5    LineNumber, LocalVariable, MethodParameter, StackMapFrame, VerificationTypeInfo,
6};
7use crate::constants;
8use crate::error::ClassWriteError;
9use crate::insn::{
10    AbstractInsnNode, FieldInsnNode, Insn, InsnNode, JumpInsnNode, JumpLabelInsnNode, Label,
11    LabelNode, LdcInsnNode, LdcValue, LineNumberInsnNode, MemberRef, MethodInsnNode, NodeList,
12    VarInsnNode,
13};
14use crate::nodes::{ClassNode, FieldNode, MethodNode};
15use crate::opcodes;
16
17/// Flag to automatically compute the stack map frames.
18///
19/// When this flag is passed to the `ClassWriter`, it calculates the `StackMapTable`
20/// attribute based on the bytecode instructions. This requires the `compute_maxs` logic as well.
21pub const COMPUTE_FRAMES: u32 = 0x1;
22
23/// Flag to automatically compute the maximum stack size and local variables.
24///
25/// When this flag is set, the writer will calculate `max_stack` and `max_locals`
26/// for methods, ignoring the values provided in `visit_maxs`.
27pub const COMPUTE_MAXS: u32 = 0x2;
28
29/// A builder for the constant pool of a class.
30///
31/// This struct manages the deduplication of constant pool entries, ensuring that
32/// strings, classes, and member references are stored efficiently.
33#[derive(Debug, Default)]
34pub struct ConstantPoolBuilder {
35    cp: Vec<CpInfo>,
36    utf8: HashMap<String, u16>,
37    class: HashMap<String, u16>,
38    string: HashMap<String, u16>,
39    name_and_type: HashMap<(String, String), u16>,
40    field_ref: HashMap<(String, String, String), u16>,
41    method_ref: HashMap<(String, String, String), u16>,
42}
43
44impl ConstantPoolBuilder {
45    /// Creates a new, empty `ConstantPoolBuilder`.
46    ///
47    /// The constant pool starts with a dummy entry at index 0, as per JVM spec.
48    pub fn new() -> Self {
49        Self {
50            cp: vec![CpInfo::Unusable],
51            ..Default::default()
52        }
53    }
54
55    /// Consumes the builder and returns the raw vector of `CpInfo` entries.
56    pub fn into_pool(self) -> Vec<CpInfo> {
57        self.cp
58    }
59
60    /// Adds a UTF-8 string to the constant pool if it doesn't exist.
61    ///
62    /// Returns the index of the entry.
63    pub fn utf8(&mut self, value: &str) -> u16 {
64        if let Some(index) = self.utf8.get(value) {
65            return *index;
66        }
67        let index = self.push(CpInfo::Utf8(value.to_string()));
68        self.utf8.insert(value.to_string(), index);
69        index
70    }
71
72    /// Adds a Class constant to the pool.
73    ///
74    /// This will recursively add the UTF-8 name of the class.
75    pub fn class(&mut self, name: &str) -> u16 {
76        if let Some(index) = self.class.get(name) {
77            return *index;
78        }
79        let name_index = self.utf8(name);
80        let index = self.push(CpInfo::Class { name_index });
81        self.class.insert(name.to_string(), index);
82        index
83    }
84
85    /// Adds a String constant to the pool.
86    ///
87    /// This is for string literals (e.g., `ldc "foo"`).
88    pub fn string(&mut self, value: &str) -> u16 {
89        if let Some(index) = self.string.get(value) {
90            return *index;
91        }
92        let string_index = self.utf8(value);
93        let index = self.push(CpInfo::String { string_index });
94        self.string.insert(value.to_string(), index);
95        index
96    }
97
98    /// Adds a NameAndType constant to the pool.
99    ///
100    /// Used for field and method descriptors.
101    pub fn name_and_type(&mut self, name: &str, descriptor: &str) -> u16 {
102        let key = (name.to_string(), descriptor.to_string());
103        if let Some(index) = self.name_and_type.get(&key) {
104            return *index;
105        }
106        let name_index = self.utf8(name);
107        let descriptor_index = self.utf8(descriptor);
108        let index = self.push(CpInfo::NameAndType {
109            name_index,
110            descriptor_index,
111        });
112        self.name_and_type.insert(key, index);
113        index
114    }
115
116    /// Adds a Fieldref constant to the pool.
117    pub fn field_ref(&mut self, owner: &str, name: &str, descriptor: &str) -> u16 {
118        let key = (owner.to_string(), name.to_string(), descriptor.to_string());
119        if let Some(index) = self.field_ref.get(&key) {
120            return *index;
121        }
122        let class_index = self.class(owner);
123        let name_and_type_index = self.name_and_type(name, descriptor);
124        let index = self.push(CpInfo::Fieldref {
125            class_index,
126            name_and_type_index,
127        });
128        self.field_ref.insert(key, index);
129        index
130    }
131
132    /// Adds a Methodref constant to the pool.
133    pub fn method_ref(&mut self, owner: &str, name: &str, descriptor: &str) -> u16 {
134        let key = (owner.to_string(), name.to_string(), descriptor.to_string());
135        if let Some(index) = self.method_ref.get(&key) {
136            return *index;
137        }
138        let class_index = self.class(owner);
139        let name_and_type_index = self.name_and_type(name, descriptor);
140        let index = self.push(CpInfo::Methodref {
141            class_index,
142            name_and_type_index,
143        });
144        self.method_ref.insert(key, index);
145        index
146    }
147
148    fn push(&mut self, entry: CpInfo) -> u16 {
149        self.cp.push(entry);
150        (self.cp.len() - 1) as u16
151    }
152}
153
154struct FieldData {
155    access_flags: u16,
156    name: String,
157    descriptor: String,
158    attributes: Vec<AttributeInfo>,
159}
160
161struct MethodData {
162    access_flags: u16,
163    name: String,
164    descriptor: String,
165    code: Option<CodeAttribute>,
166    attributes: Vec<AttributeInfo>,
167}
168
169/// A writer that generates a Java Class File structure.
170///
171/// This is the main entry point for creating class files programmatically.
172/// It allows visiting the class header, fields, methods, and attributes.
173///
174/// # Example
175///
176/// ```rust
177/// use rust_asm::{class_writer::{ClassWriter, COMPUTE_FRAMES}, opcodes};
178///
179/// let mut cw = ClassWriter::new(COMPUTE_FRAMES);
180/// cw.visit(52, 0, 1, "com/example/MyClass", Some("java/lang/Object"), &[]);
181///
182/// let mut mv = cw.visit_method(1, "myMethod", "()V");
183/// mv.visit_code();
184/// mv.visit_insn(opcodes::RETURN);
185/// mv.visit_maxs(0, 0); // Computed automatically due to COMPUTE_FRAMES
186///
187/// let bytes = cw.to_bytes().unwrap();
188/// ```
189pub struct ClassWriter {
190    options: u32,
191    minor_version: u16,
192    major_version: u16,
193    access_flags: u16,
194    name: String,
195    super_name: Option<String>,
196    interfaces: Vec<String>,
197    fields: Vec<FieldData>,
198    methods: Vec<MethodData>,
199    attributes: Vec<AttributeInfo>,
200    source_file: Option<String>,
201    cp: ConstantPoolBuilder,
202}
203
204impl ClassWriter {
205    /// Creates a new `ClassWriter`.
206    ///
207    /// # Arguments
208    ///
209    /// * `options` - Bitwise flags to control generation (e.g., `COMPUTE_FRAMES`, `COMPUTE_MAXS`).
210    pub fn new(options: u32) -> Self {
211        Self {
212            options,
213            minor_version: 0,
214            major_version: 52,
215            access_flags: 0,
216            name: String::new(),
217            super_name: None,
218            interfaces: Vec::new(),
219            fields: Vec::new(),
220            methods: Vec::new(),
221            attributes: Vec::new(),
222            source_file: None,
223            cp: ConstantPoolBuilder::new(),
224        }
225    }
226
227    /// Defines the header of the class.
228    ///
229    /// # Arguments
230    ///
231    /// * `major` - The major version (e.g., 52 for Java 8).
232    /// * `minor` - The minor version.
233    /// * `access_flags` - Access modifiers (e.g., public, final).
234    /// * `name` - The internal name of the class (e.g., "java/lang/String").
235    /// * `super_name` - The internal name of the super class (None for Object).
236    /// * `interfaces` - A list of interfaces implemented by this class.
237    pub fn visit(
238        &mut self,
239        major: u16,
240        minor: u16,
241        access_flags: u16,
242        name: &str,
243        super_name: Option<&str>,
244        interfaces: &[&str],
245    ) -> &mut Self {
246        self.major_version = major;
247        self.minor_version = minor;
248        self.access_flags = access_flags;
249        self.name = name.to_string();
250        self.super_name = super_name.map(|value| value.to_string());
251        self.interfaces = interfaces
252            .iter()
253            .map(|value| (*value).to_string())
254            .collect();
255        self
256    }
257
258    /// Sets the source file name attribute for the class.
259    pub fn visit_source_file(&mut self, name: &str) -> &mut Self {
260        self.source_file = Some(name.to_string());
261        self
262    }
263
264    /// Visits a method of the class.
265    ///
266    /// Returns a `MethodVisitor` that should be used to define the method body.
267    /// The `visit_end` method of the returned visitor must be called to attach it to the class.
268    pub fn visit_method(
269        &mut self,
270        access_flags: u16,
271        name: &str,
272        descriptor: &str,
273    ) -> MethodVisitor {
274        MethodVisitor::new(access_flags, name, descriptor)
275    }
276
277    /// Visits a field of the class.
278    ///
279    /// Returns a `FieldVisitor` to define field attributes.
280    pub fn visit_field(&mut self, access_flags: u16, name: &str, descriptor: &str) -> FieldVisitor {
281        FieldVisitor::new(access_flags, name, descriptor)
282    }
283
284    /// Adds a custom attribute to the class.
285    pub fn add_attribute(&mut self, attr: AttributeInfo) -> &mut Self {
286        self.attributes.push(attr);
287        self
288    }
289
290    /// Converts the builder state into a `ClassNode` object model.
291    pub fn to_class_node(mut self) -> Result<ClassNode, String> {
292        if self.name.is_empty() {
293            return Err("missing class name, call visit() first".to_string());
294        }
295
296        let this_class = self.cp.class(&self.name);
297        let super_class = match self.super_name.as_deref() {
298            Some(name) => self.cp.class(name),
299            None => 0,
300        };
301
302        let mut interface_indices = Vec::with_capacity(self.interfaces.len());
303        for name in &self.interfaces {
304            interface_indices.push(self.cp.class(name));
305        }
306
307        let mut fields = Vec::with_capacity(self.fields.len());
308        for field in self.fields {
309            let name_index = self.cp.utf8(&field.name);
310            let descriptor_index = self.cp.utf8(&field.descriptor);
311            fields.push(FieldNode {
312                access_flags: field.access_flags,
313                name_index,
314                descriptor_index,
315                name: field.name,
316                descriptor: field.descriptor,
317                attributes: field.attributes,
318            });
319        }
320
321        let mut methods = Vec::with_capacity(self.methods.len());
322        for method in self.methods {
323            let name_index = self.cp.utf8(&method.name);
324            let descriptor_index = self.cp.utf8(&method.descriptor);
325            methods.push(MethodNode {
326                access_flags: method.access_flags,
327                name_index,
328                descriptor_index,
329                name: method.name,
330                descriptor: method.descriptor,
331                code: method.code,
332                attributes: method.attributes,
333            });
334        }
335
336        if let Some(source_name) = self.source_file.as_ref() {
337            let source_index = self.cp.utf8(source_name);
338            self.attributes.push(AttributeInfo::SourceFile {
339                sourcefile_index: source_index,
340            });
341        }
342
343        Ok(ClassNode {
344            minor_version: self.minor_version,
345            major_version: self.major_version,
346            access_flags: self.access_flags,
347            constant_pool: self.cp.into_pool(),
348            this_class,
349            super_class,
350            name: self.name,
351            super_name: self.super_name,
352            source_file: self.source_file.clone(),
353            interfaces: self.interfaces,
354            interface_indices,
355            fields,
356            methods,
357            attributes: self.attributes,
358        })
359    }
360
361    /// Generates the raw byte vector representing the .class file.
362    ///
363    /// This method performs all necessary computations (stack map frames, max stack size)
364    /// based on the options provided in `new`.
365    pub fn to_bytes(self) -> Result<Vec<u8>, ClassWriteError> {
366        let options = self.options;
367        let class_node = self
368            .to_class_node()
369            .map_err(ClassWriteError::FrameComputation)?;
370        ClassFileWriter::new(options).to_bytes(&class_node)
371    }
372
373    pub fn write_class_node(
374        class_node: &ClassNode,
375        options: u32,
376    ) -> Result<Vec<u8>, ClassWriteError> {
377        ClassFileWriter::new(options).to_bytes(class_node)
378    }
379}
380
381/// A visitor to visit a Java method.
382///
383/// Used to generate the bytecode instructions, exception tables, and attributes
384/// for a specific method.
385pub struct MethodVisitor {
386    access_flags: u16,
387    name: String,
388    descriptor: String,
389    has_code: bool,
390    max_stack: u16,
391    max_locals: u16,
392    insns: NodeList,
393    exception_table: Vec<ExceptionTableEntry>,
394    code_attributes: Vec<AttributeInfo>,
395    attributes: Vec<AttributeInfo>,
396}
397
398impl MethodVisitor {
399    pub fn new(access_flags: u16, name: &str, descriptor: &str) -> Self {
400        Self {
401            access_flags,
402            name: name.to_string(),
403            descriptor: descriptor.to_string(),
404            has_code: false,
405            max_stack: 0,
406            max_locals: 0,
407            insns: NodeList::new(),
408            exception_table: Vec::new(),
409            code_attributes: Vec::new(),
410            attributes: Vec::new(),
411        }
412    }
413
414    /// Starts the visit of the method's code.
415    pub fn visit_code(&mut self) -> &mut Self {
416        self.has_code = true;
417        self
418    }
419
420    /// Visits a zero-operand instruction (e.g., NOP, RETURN).
421    pub fn visit_insn(&mut self, opcode: u8) -> &mut Self {
422        self.insns.add(Insn::from(Into::<InsnNode>::into(opcode)));
423        self
424    }
425
426    /// Visits a local variable instruction (e.g., ILOAD, ASTORE).
427    pub fn visit_var_insn(&mut self, opcode: u8, var_index: u16) -> &mut Self {
428        self.insns.add(Insn::Var(VarInsnNode {
429            insn: opcode.into(),
430            var_index,
431        }));
432        self
433    }
434
435    /// Visits a field instruction (e.g., GETFIELD, PUTSTATIC).
436    pub fn visit_field_insn(
437        &mut self,
438        opcode: u8,
439        owner: &str,
440        name: &str,
441        descriptor: &str,
442    ) -> &mut Self {
443        self.insns.add(Insn::Field(FieldInsnNode::new(
444            opcode, owner, name, descriptor,
445        )));
446        self
447    }
448
449    /// Visits a method instruction (e.g., INVOKEVIRTUAL).
450    pub fn visit_method_insn(
451        &mut self,
452        opcode: u8,
453        owner: &str,
454        name: &str,
455        descriptor: &str,
456        _is_interface: bool,
457    ) -> &mut Self {
458        self.insns.add(Insn::Method(MethodInsnNode::new(
459            opcode, owner, name, descriptor,
460        )));
461        self
462    }
463
464    pub fn visit_jump_insn(&mut self, opcode: u8, target: Label) -> &mut Self {
465        self.insns.add(JumpLabelInsnNode {
466            insn: opcode.into(),
467            target: LabelNode::from_label(target),
468        });
469        self
470    }
471
472    pub fn visit_label(&mut self, label: Label) -> &mut Self {
473        self.insns.add(LabelNode::from_label(label));
474        self
475    }
476
477    pub fn visit_line_number(&mut self, line: u16, start: LabelNode) -> &mut Self {
478        self.insns.add(LineNumberInsnNode::new(line, start));
479        self
480    }
481
482    /// Visits a constant instruction (LDC).
483    pub fn visit_ldc_insn(&mut self, value: &str) -> &mut Self {
484        self.insns.add(Insn::Ldc(LdcInsnNode::string(value)));
485        self
486    }
487
488    /// Visits the maximum stack size and number of local variables.
489    ///
490    /// If `COMPUTE_MAXS` or `COMPUTE_FRAMES` was passed to the ClassWriter,
491    /// these values may be ignored or recomputed.
492    pub fn visit_maxs(&mut self, max_stack: u16, max_locals: u16) -> &mut Self {
493        self.max_stack = max_stack;
494        self.max_locals = max_locals;
495        self
496    }
497
498    /// Finalizes the method and attaches it to the parent `ClassWriter`.
499    pub fn visit_end(mut self, class: &mut ClassWriter) {
500        let code = if self.has_code || !self.insns.nodes().is_empty() {
501            Some(build_code_attribute(
502                self.max_stack,
503                self.max_locals,
504                self.insns,
505                &mut class.cp,
506                std::mem::take(&mut self.exception_table),
507                std::mem::take(&mut self.code_attributes),
508            ))
509        } else {
510            None
511        };
512        class.methods.push(MethodData {
513            access_flags: self.access_flags,
514            name: self.name,
515            descriptor: self.descriptor,
516            code,
517            attributes: std::mem::take(&mut self.attributes),
518        });
519    }
520}
521
522/// A visitor to visit a Java field.
523pub struct FieldVisitor {
524    access_flags: u16,
525    name: String,
526    descriptor: String,
527    attributes: Vec<AttributeInfo>,
528}
529
530impl FieldVisitor {
531    pub fn new(access_flags: u16, name: &str, descriptor: &str) -> Self {
532        Self {
533            access_flags,
534            name: name.to_string(),
535            descriptor: descriptor.to_string(),
536            attributes: Vec::new(),
537        }
538    }
539
540    /// Adds an attribute to the field.
541    pub fn add_attribute(&mut self, attr: AttributeInfo) -> &mut Self {
542        self.attributes.push(attr);
543        self
544    }
545
546    /// Finalizes the field and attaches it to the parent `ClassWriter`.
547    pub fn visit_end(self, class: &mut ClassWriter) {
548        class.fields.push(FieldData {
549            access_flags: self.access_flags,
550            name: self.name,
551            descriptor: self.descriptor,
552            attributes: self.attributes,
553        });
554    }
555}
556
557struct CodeBody {
558    max_stack: u16,
559    max_locals: u16,
560    insns: NodeList,
561    exception_table: Vec<ExceptionTableEntry>,
562    attributes: Vec<AttributeInfo>,
563}
564
565impl CodeBody {
566    fn new(max_stack: u16, max_locals: u16, insns: NodeList) -> Self {
567        Self {
568            max_stack,
569            max_locals,
570            insns,
571            exception_table: Vec::new(),
572            attributes: Vec::new(),
573        }
574    }
575
576    fn build(self, cp: &mut ConstantPoolBuilder) -> CodeAttribute {
577        let mut code = Vec::new();
578        let mut instructions = Vec::new();
579        let mut insn_nodes = Vec::new();
580        let mut label_offsets: HashMap<usize, u16> = HashMap::new();
581        let mut pending_lines: Vec<LineNumberInsnNode> = Vec::new();
582        let mut jump_fixups: Vec<JumpFixup> = Vec::new();
583        for node in self.insns.into_nodes() {
584            match node {
585                AbstractInsnNode::Insn(insn) => {
586                    let resolved = emit_insn(&mut code, insn, cp);
587                    instructions.push(resolved.clone());
588                    insn_nodes.push(AbstractInsnNode::Insn(resolved));
589                }
590                AbstractInsnNode::JumpLabel(node) => {
591                    let opcode = node.insn.opcode;
592                    let start = code.len();
593                    code.push(opcode);
594                    if is_wide_jump(opcode) {
595                        write_i4(&mut code, 0);
596                    } else {
597                        write_i2(&mut code, 0);
598                    }
599                    let insn = Insn::Jump(JumpInsnNode {
600                        insn: InsnNode { opcode },
601                        offset: 0,
602                    });
603                    instructions.push(insn.clone());
604                    insn_nodes.push(AbstractInsnNode::Insn(insn.clone()));
605                    jump_fixups.push(JumpFixup {
606                        start,
607                        opcode,
608                        target: node.target,
609                        insn_index: instructions.len() - 1,
610                        node_index: insn_nodes.len() - 1,
611                    });
612                }
613                AbstractInsnNode::Label(label) => {
614                    let offset = code.len();
615                    if offset <= u16::MAX as usize {
616                        label_offsets.insert(label.id, offset as u16);
617                    }
618                    insn_nodes.push(AbstractInsnNode::Label(label));
619                }
620                AbstractInsnNode::LineNumber(line) => {
621                    pending_lines.push(line);
622                    insn_nodes.push(AbstractInsnNode::LineNumber(line));
623                }
624            }
625        }
626        for fixup in jump_fixups {
627            if let Some(target_offset) = label_offsets.get(&fixup.target.id) {
628                let offset = *target_offset as i32 - fixup.start as i32;
629                if is_wide_jump(fixup.opcode) {
630                    write_i4_at(&mut code, fixup.start + 1, offset);
631                } else {
632                    write_i2_at(&mut code, fixup.start + 1, offset as i16);
633                }
634                let resolved = Insn::Jump(JumpInsnNode {
635                    insn: InsnNode { opcode: fixup.opcode },
636                    offset,
637                });
638                instructions[fixup.insn_index] = resolved.clone();
639                insn_nodes[fixup.node_index] = AbstractInsnNode::Insn(resolved);
640            }
641        }
642        let mut attributes = self.attributes;
643        if !pending_lines.is_empty() {
644            let mut entries = Vec::new();
645            for line in pending_lines {
646                if let Some(start_pc) = label_offsets.get(&line.start.id) {
647                    entries.push(LineNumber {
648                        start_pc: *start_pc,
649                        line_number: line.line,
650                    });
651                }
652            }
653            if !entries.is_empty() {
654                attributes.push(AttributeInfo::LineNumberTable { entries });
655            }
656        }
657        CodeAttribute {
658            max_stack: self.max_stack,
659            max_locals: self.max_locals,
660            code,
661            instructions,
662            insn_nodes,
663            exception_table: self.exception_table,
664            try_catch_blocks: Vec::new(),
665            attributes,
666        }
667    }
668}
669
670#[derive(Debug, Clone, Copy)]
671struct JumpFixup {
672    start: usize,
673    opcode: u8,
674    target: LabelNode,
675    insn_index: usize,
676    node_index: usize,
677}
678
679fn is_wide_jump(opcode: u8) -> bool {
680    matches!(opcode, opcodes::GOTO_W | opcodes::JSR_W)
681}
682
683fn jump_size(opcode: u8) -> usize {
684    if is_wide_jump(opcode) {
685        5
686    } else {
687        3
688    }
689}
690fn build_code_attribute(
691    max_stack: u16,
692    max_locals: u16,
693    insns: NodeList,
694    cp: &mut ConstantPoolBuilder,
695    exception_table: Vec<ExceptionTableEntry>,
696    attributes: Vec<AttributeInfo>,
697) -> CodeAttribute {
698    CodeBody {
699        max_stack,
700        max_locals,
701        insns,
702        exception_table,
703        attributes,
704    }
705    .build(cp)
706}
707
708fn emit_insn(code: &mut Vec<u8>, insn: Insn, cp: &mut ConstantPoolBuilder) -> Insn {
709    let offset = code.len();
710    match insn {
711        Insn::Simple(node) => {
712            code.push(node.opcode);
713            Insn::Simple(node)
714        }
715        Insn::Int(node) => {
716            code.push(node.insn.opcode);
717            match node.insn.opcode {
718                opcodes::BIPUSH => write_i1(code, node.operand as i8),
719                opcodes::SIPUSH => write_i2(code, node.operand as i16),
720                opcodes::NEWARRAY => write_u1(code, node.operand as u8),
721                _ => write_i1(code, node.operand as i8),
722            }
723            Insn::Int(node)
724        }
725        Insn::Var(node) => {
726            code.push(node.insn.opcode);
727            write_u1(code, node.var_index as u8);
728            Insn::Var(node)
729        }
730        Insn::Type(node) => {
731            code.push(node.insn.opcode);
732            write_u2(code, node.type_index);
733            Insn::Type(node)
734        }
735        Insn::Field(node) => {
736            code.push(node.insn.opcode);
737            let (index, resolved) = resolve_field_ref(node, cp);
738            write_u2(code, index);
739            Insn::Field(resolved)
740        }
741        Insn::Method(node) => {
742            code.push(node.insn.opcode);
743            let (index, resolved) = resolve_method_ref(node, cp);
744            write_u2(code, index);
745            Insn::Method(resolved)
746        }
747        Insn::InvokeInterface(node) => {
748            code.push(node.insn.opcode);
749            write_u2(code, node.method_index);
750            write_u1(code, node.count);
751            write_u1(code, 0);
752            Insn::InvokeInterface(node)
753        }
754        Insn::InvokeDynamic(node) => {
755            code.push(node.insn.opcode);
756            write_u2(code, node.method_index);
757            write_u2(code, 0);
758            Insn::InvokeDynamic(node)
759        }
760        Insn::Jump(node) => {
761            code.push(node.insn.opcode);
762            match node.insn.opcode {
763                opcodes::GOTO_W | opcodes::JSR_W => write_i4(code, node.offset),
764                _ => write_i2(code, node.offset as i16),
765            }
766            Insn::Jump(node)
767        }
768        Insn::Ldc(node) => {
769            let (opcode, index, resolved) = resolve_ldc(node, cp);
770            code.push(opcode);
771            if opcode == opcodes::LDC {
772                write_u1(code, index as u8);
773            } else {
774                write_u2(code, index);
775            }
776            Insn::Ldc(resolved)
777        }
778        Insn::Iinc(node) => {
779            code.push(node.insn.opcode);
780            write_u1(code, node.var_index as u8);
781            write_i1(code, node.increment as i8);
782            Insn::Iinc(node)
783        }
784        Insn::TableSwitch(node) => {
785            code.push(node.insn.opcode);
786            write_switch_padding(code, offset);
787            write_i4(code, node.default_offset);
788            write_i4(code, node.low);
789            write_i4(code, node.high);
790            for value in &node.offsets {
791                write_i4(code, *value);
792            }
793            Insn::TableSwitch(node)
794        }
795        Insn::LookupSwitch(node) => {
796            code.push(node.insn.opcode);
797            write_switch_padding(code, offset);
798            write_i4(code, node.default_offset);
799            write_i4(code, node.pairs.len() as i32);
800            for (key, value) in &node.pairs {
801                write_i4(code, *key);
802                write_i4(code, *value);
803            }
804            Insn::LookupSwitch(node)
805        }
806        Insn::MultiANewArray(node) => {
807            code.push(node.insn.opcode);
808            write_u2(code, node.type_index);
809            write_u1(code, node.dimensions);
810            Insn::MultiANewArray(node)
811        }
812    }
813}
814
815fn resolve_field_ref(node: FieldInsnNode, cp: &mut ConstantPoolBuilder) -> (u16, FieldInsnNode) {
816    match node.field_ref {
817        MemberRef::Index(index) => (index, node),
818        MemberRef::Symbolic {
819            owner,
820            name,
821            descriptor,
822        } => {
823            let index = cp.field_ref(&owner, &name, &descriptor);
824            (
825                index,
826                FieldInsnNode {
827                    insn: node.insn,
828                    field_ref: MemberRef::Index(index),
829                },
830            )
831        }
832    }
833}
834
835fn resolve_method_ref(node: MethodInsnNode, cp: &mut ConstantPoolBuilder) -> (u16, MethodInsnNode) {
836    match node.method_ref {
837        MemberRef::Index(index) => (index, node),
838        MemberRef::Symbolic {
839            owner,
840            name,
841            descriptor,
842        } => {
843            let index = cp.method_ref(&owner, &name, &descriptor);
844            (
845                index,
846                MethodInsnNode {
847                    insn: node.insn,
848                    method_ref: MemberRef::Index(index),
849                },
850            )
851        }
852    }
853}
854
855fn resolve_ldc(node: LdcInsnNode, cp: &mut ConstantPoolBuilder) -> (u8, u16, LdcInsnNode) {
856    match node.value {
857        LdcValue::Index(index) => {
858            let opcode = if index <= 0xFF {
859                opcodes::LDC
860            } else {
861                opcodes::LDC_W
862            };
863            (
864                opcode,
865                index,
866                LdcInsnNode {
867                    insn: opcode.into(),
868                    value: LdcValue::Index(index),
869                },
870            )
871        }
872        LdcValue::String(value) => {
873            let index = cp.string(&value);
874            let opcode = if index <= 0xFF {
875                opcodes::LDC
876            } else {
877                opcodes::LDC_W
878            };
879            (
880                opcode,
881                index,
882                LdcInsnNode {
883                    insn: opcode.into(),
884                    value: LdcValue::Index(index),
885                },
886            )
887        }
888    }
889}
890
891struct ClassFileWriter {
892    options: u32,
893}
894
895impl ClassFileWriter {
896    fn new(options: u32) -> Self {
897        Self { options }
898    }
899
900    fn to_bytes(&self, class_node: &ClassNode) -> Result<Vec<u8>, ClassWriteError> {
901        if class_node.constant_pool.is_empty() {
902            return Err(ClassWriteError::MissingConstantPool);
903        }
904
905        let mut cp = class_node.constant_pool.clone();
906        let mut out = Vec::new();
907        write_u4(&mut out, 0xCAFEBABE);
908        write_u2(&mut out, class_node.minor_version);
909        write_u2(&mut out, class_node.major_version);
910
911        let mut class_attributes = class_node.attributes.clone();
912        if let Some(source_file) = &class_node.source_file {
913            class_attributes.retain(|attr| !matches!(attr, AttributeInfo::SourceFile { .. }));
914            let source_index = ensure_utf8(&mut cp, source_file);
915            class_attributes.push(AttributeInfo::SourceFile {
916                sourcefile_index: source_index,
917            });
918        }
919
920        let mut attribute_names = Vec::new();
921        collect_attribute_names(&class_attributes, &mut attribute_names);
922        for field in &class_node.fields {
923            collect_attribute_names(&field.attributes, &mut attribute_names);
924        }
925        for method in &class_node.methods {
926            collect_attribute_names(&method.attributes, &mut attribute_names);
927            if let Some(code) = &method.code {
928                attribute_names.push("Code".to_string());
929                collect_attribute_names(&code.attributes, &mut attribute_names);
930            }
931        }
932        for name in attribute_names {
933            ensure_utf8(&mut cp, &name);
934        }
935
936        let mut precomputed_stack_maps: Vec<Option<Vec<StackMapFrame>>> =
937            Vec::with_capacity(class_node.methods.len());
938        let mut precomputed_maxs: Vec<Option<(u16, u16)>> =
939            Vec::with_capacity(class_node.methods.len());
940        let compute_frames = self.options & COMPUTE_FRAMES != 0;
941        let compute_maxs_flag = self.options & COMPUTE_MAXS != 0;
942        if compute_frames {
943            ensure_utf8(&mut cp, "StackMapTable");
944            for method in &class_node.methods {
945                if let Some(code) = &method.code {
946                    let maxs = if compute_maxs_flag {
947                        Some(compute_maxs(method, class_node, code, &cp)?)
948                    } else {
949                        None
950                    };
951                    let max_locals = maxs.map(|item| item.1).unwrap_or(code.max_locals);
952                    let stack_map =
953                        compute_stack_map_table(method, class_node, code, &mut cp, max_locals)?;
954                    precomputed_stack_maps.push(Some(stack_map));
955                    precomputed_maxs.push(maxs);
956                } else {
957                    precomputed_stack_maps.push(None);
958                    precomputed_maxs.push(None);
959                }
960            }
961        } else if compute_maxs_flag {
962            for method in &class_node.methods {
963                if let Some(code) = &method.code {
964                    precomputed_maxs.push(Some(compute_maxs(method, class_node, code, &cp)?));
965                } else {
966                    precomputed_maxs.push(None);
967                }
968            }
969            precomputed_stack_maps.resize(class_node.methods.len(), None);
970        } else {
971            precomputed_stack_maps.resize(class_node.methods.len(), None);
972            precomputed_maxs.resize(class_node.methods.len(), None);
973        }
974
975        write_constant_pool(&mut out, &cp)?;
976        write_u2(&mut out, class_node.access_flags);
977        write_u2(&mut out, class_node.this_class);
978        write_u2(&mut out, class_node.super_class);
979        write_u2(&mut out, class_node.interface_indices.len() as u16);
980        for index in &class_node.interface_indices {
981            write_u2(&mut out, *index);
982        }
983
984        write_u2(&mut out, class_node.fields.len() as u16);
985        for field in &class_node.fields {
986            write_field(&mut out, field, &mut cp)?;
987        }
988
989        write_u2(&mut out, class_node.methods.len() as u16);
990        for (index, method) in class_node.methods.iter().enumerate() {
991            let stack_map = precomputed_stack_maps
992                .get(index)
993                .and_then(|item| item.as_ref());
994            let maxs = precomputed_maxs.get(index).and_then(|item| *item);
995            write_method(
996                &mut out,
997                method,
998                class_node,
999                &mut cp,
1000                self.options,
1001                stack_map,
1002                maxs,
1003            )?;
1004        }
1005
1006        write_u2(&mut out, class_attributes.len() as u16);
1007        for attr in &class_attributes {
1008            write_attribute(&mut out, attr, &mut cp, None, self.options, None, None)?;
1009        }
1010
1011        Ok(out)
1012    }
1013}
1014
1015fn write_field(
1016    out: &mut Vec<u8>,
1017    field: &FieldNode,
1018    cp: &mut Vec<CpInfo>,
1019) -> Result<(), ClassWriteError> {
1020    write_u2(out, field.access_flags);
1021    write_u2(out, field.name_index);
1022    write_u2(out, field.descriptor_index);
1023    write_u2(out, field.attributes.len() as u16);
1024    for attr in &field.attributes {
1025        write_attribute(out, attr, cp, None, 0, None, None)?;
1026    }
1027    Ok(())
1028}
1029
1030fn write_method(
1031    out: &mut Vec<u8>,
1032    method: &MethodNode,
1033    class_node: &ClassNode,
1034    cp: &mut Vec<CpInfo>,
1035    options: u32,
1036    precomputed_stack_map: Option<&Vec<StackMapFrame>>,
1037    precomputed_maxs: Option<(u16, u16)>,
1038) -> Result<(), ClassWriteError> {
1039    write_u2(out, method.access_flags);
1040    write_u2(out, method.name_index);
1041    write_u2(out, method.descriptor_index);
1042
1043    let mut attributes = method.attributes.clone();
1044    if let Some(code) = &method.code {
1045        attributes.retain(|attr| !matches!(attr, AttributeInfo::Code(_)));
1046        attributes.push(AttributeInfo::Code(code.clone()));
1047    }
1048
1049    write_u2(out, attributes.len() as u16);
1050    for attr in &attributes {
1051        write_attribute(
1052            out,
1053            attr,
1054            cp,
1055            Some((method, class_node)),
1056            options,
1057            precomputed_stack_map,
1058            precomputed_maxs,
1059        )?;
1060    }
1061    Ok(())
1062}
1063
1064fn write_attribute(
1065    out: &mut Vec<u8>,
1066    attr: &AttributeInfo,
1067    cp: &mut Vec<CpInfo>,
1068    method_ctx: Option<(&MethodNode, &ClassNode)>,
1069    options: u32,
1070    precomputed_stack_map: Option<&Vec<StackMapFrame>>,
1071    precomputed_maxs: Option<(u16, u16)>,
1072) -> Result<(), ClassWriteError> {
1073    match attr {
1074        AttributeInfo::Code(code) => {
1075            let name_index = ensure_utf8(cp, "Code");
1076            let mut info = Vec::new();
1077            let mut code_attributes = code.attributes.clone();
1078            let (max_stack, max_locals) =
1079                precomputed_maxs.unwrap_or((code.max_stack, code.max_locals));
1080            if options & COMPUTE_FRAMES != 0 {
1081                code_attributes.retain(|item| !matches!(item, AttributeInfo::StackMapTable { .. }));
1082                let stack_map = if let Some(precomputed) = precomputed_stack_map {
1083                    precomputed.clone()
1084                } else {
1085                    let (method, class_node) = method_ctx.ok_or_else(|| {
1086                        ClassWriteError::FrameComputation("missing method".to_string())
1087                    })?;
1088                    compute_stack_map_table(method, class_node, code, cp, max_locals)?
1089                };
1090                code_attributes.push(AttributeInfo::StackMapTable { entries: stack_map });
1091            }
1092
1093            write_u2(&mut info, max_stack);
1094            write_u2(&mut info, max_locals);
1095            write_u4(&mut info, code.code.len() as u32);
1096            info.extend_from_slice(&code.code);
1097            write_u2(&mut info, code.exception_table.len() as u16);
1098            for entry in &code.exception_table {
1099                write_exception_table_entry(&mut info, entry);
1100            }
1101            write_u2(&mut info, code_attributes.len() as u16);
1102            for nested in &code_attributes {
1103                write_attribute(&mut info, nested, cp, method_ctx, options, None, None)?;
1104            }
1105            write_attribute_with_info(out, name_index, &info);
1106        }
1107        AttributeInfo::ConstantValue {
1108            constantvalue_index,
1109        } => {
1110            let name_index = ensure_utf8(cp, "ConstantValue");
1111            let mut info = Vec::new();
1112            write_u2(&mut info, *constantvalue_index);
1113            write_attribute_with_info(out, name_index, &info);
1114        }
1115        AttributeInfo::Exceptions {
1116            exception_index_table,
1117        } => {
1118            let name_index = ensure_utf8(cp, "Exceptions");
1119            let mut info = Vec::new();
1120            write_u2(&mut info, exception_index_table.len() as u16);
1121            for index in exception_index_table {
1122                write_u2(&mut info, *index);
1123            }
1124            write_attribute_with_info(out, name_index, &info);
1125        }
1126        AttributeInfo::SourceFile { sourcefile_index } => {
1127            let name_index = ensure_utf8(cp, "SourceFile");
1128            let mut info = Vec::new();
1129            write_u2(&mut info, *sourcefile_index);
1130            write_attribute_with_info(out, name_index, &info);
1131        }
1132        AttributeInfo::LineNumberTable { entries } => {
1133            let name_index = ensure_utf8(cp, "LineNumberTable");
1134            let mut info = Vec::new();
1135            write_u2(&mut info, entries.len() as u16);
1136            for entry in entries {
1137                write_line_number(&mut info, entry);
1138            }
1139            write_attribute_with_info(out, name_index, &info);
1140        }
1141        AttributeInfo::LocalVariableTable { entries } => {
1142            let name_index = ensure_utf8(cp, "LocalVariableTable");
1143            let mut info = Vec::new();
1144            write_u2(&mut info, entries.len() as u16);
1145            for entry in entries {
1146                write_local_variable(&mut info, entry);
1147            }
1148            write_attribute_with_info(out, name_index, &info);
1149        }
1150        AttributeInfo::Signature { signature_index } => {
1151            let name_index = ensure_utf8(cp, "Signature");
1152            let mut info = Vec::new();
1153            write_u2(&mut info, *signature_index);
1154            write_attribute_with_info(out, name_index, &info);
1155        }
1156        AttributeInfo::StackMapTable { entries } => {
1157            let name_index = ensure_utf8(cp, "StackMapTable");
1158            let mut info = Vec::new();
1159            write_u2(&mut info, entries.len() as u16);
1160            for entry in entries {
1161                write_stack_map_frame(&mut info, entry);
1162            }
1163            write_attribute_with_info(out, name_index, &info);
1164        }
1165        AttributeInfo::Deprecated => {
1166            let name_index = ensure_utf8(cp, "Deprecated");
1167            write_attribute_with_info(out, name_index, &[]);
1168        }
1169        AttributeInfo::Synthetic => {
1170            let name_index = ensure_utf8(cp, "Synthetic");
1171            write_attribute_with_info(out, name_index, &[]);
1172        }
1173        AttributeInfo::InnerClasses { classes } => {
1174            let name_index = ensure_utf8(cp, "InnerClasses");
1175            let mut info = Vec::new();
1176            write_u2(&mut info, classes.len() as u16);
1177            for class in classes {
1178                write_inner_class(&mut info, class);
1179            }
1180            write_attribute_with_info(out, name_index, &info);
1181        }
1182        AttributeInfo::EnclosingMethod {
1183            class_index,
1184            method_index,
1185        } => {
1186            let name_index = ensure_utf8(cp, "EnclosingMethod");
1187            let mut info = Vec::new();
1188            write_u2(&mut info, *class_index);
1189            write_u2(&mut info, *method_index);
1190            write_attribute_with_info(out, name_index, &info);
1191        }
1192        AttributeInfo::BootstrapMethods { methods } => {
1193            let name_index = ensure_utf8(cp, "BootstrapMethods");
1194            let mut info = Vec::new();
1195            write_u2(&mut info, methods.len() as u16);
1196            for method in methods {
1197                write_bootstrap_method(&mut info, method);
1198            }
1199            write_attribute_with_info(out, name_index, &info);
1200        }
1201        AttributeInfo::MethodParameters { parameters } => {
1202            let name_index = ensure_utf8(cp, "MethodParameters");
1203            let mut info = Vec::new();
1204            write_u1(&mut info, parameters.len() as u8);
1205            for parameter in parameters {
1206                write_method_parameter(&mut info, parameter);
1207            }
1208            write_attribute_with_info(out, name_index, &info);
1209        }
1210        AttributeInfo::Unknown { name, info } => {
1211            let name_index = ensure_utf8(cp, name);
1212            write_attribute_with_info(out, name_index, info);
1213        }
1214    }
1215
1216    Ok(())
1217}
1218
1219fn write_attribute_with_info(out: &mut Vec<u8>, name_index: u16, info: &[u8]) {
1220    write_u2(out, name_index);
1221    write_u4(out, info.len() as u32);
1222    out.extend_from_slice(info);
1223}
1224
1225fn write_exception_table_entry(out: &mut Vec<u8>, entry: &ExceptionTableEntry) {
1226    write_u2(out, entry.start_pc);
1227    write_u2(out, entry.end_pc);
1228    write_u2(out, entry.handler_pc);
1229    write_u2(out, entry.catch_type);
1230}
1231
1232fn write_line_number(out: &mut Vec<u8>, entry: &LineNumber) {
1233    write_u2(out, entry.start_pc);
1234    write_u2(out, entry.line_number);
1235}
1236
1237fn write_local_variable(out: &mut Vec<u8>, entry: &LocalVariable) {
1238    write_u2(out, entry.start_pc);
1239    write_u2(out, entry.length);
1240    write_u2(out, entry.name_index);
1241    write_u2(out, entry.descriptor_index);
1242    write_u2(out, entry.index);
1243}
1244
1245fn write_inner_class(out: &mut Vec<u8>, entry: &InnerClass) {
1246    write_u2(out, entry.inner_class_info_index);
1247    write_u2(out, entry.outer_class_info_index);
1248    write_u2(out, entry.inner_name_index);
1249    write_u2(out, entry.inner_class_access_flags);
1250}
1251
1252fn write_bootstrap_method(out: &mut Vec<u8>, entry: &BootstrapMethod) {
1253    write_u2(out, entry.bootstrap_method_ref);
1254    write_u2(out, entry.bootstrap_arguments.len() as u16);
1255    for arg in &entry.bootstrap_arguments {
1256        write_u2(out, *arg);
1257    }
1258}
1259
1260fn write_method_parameter(out: &mut Vec<u8>, entry: &MethodParameter) {
1261    write_u2(out, entry.name_index);
1262    write_u2(out, entry.access_flags);
1263}
1264
1265fn write_stack_map_frame(out: &mut Vec<u8>, frame: &StackMapFrame) {
1266    match frame {
1267        StackMapFrame::SameFrame { offset_delta } => {
1268            write_u1(out, *offset_delta as u8);
1269        }
1270        StackMapFrame::SameLocals1StackItemFrame {
1271            offset_delta,
1272            stack,
1273        } => {
1274            write_u1(out, (*offset_delta as u8) + 64);
1275            write_verification_type(out, stack);
1276        }
1277        StackMapFrame::SameLocals1StackItemFrameExtended {
1278            offset_delta,
1279            stack,
1280        } => {
1281            write_u1(out, 247);
1282            write_u2(out, *offset_delta);
1283            write_verification_type(out, stack);
1284        }
1285        StackMapFrame::ChopFrame { offset_delta, k } => {
1286            write_u1(out, 251 - *k);
1287            write_u2(out, *offset_delta);
1288        }
1289        StackMapFrame::SameFrameExtended { offset_delta } => {
1290            write_u1(out, 251);
1291            write_u2(out, *offset_delta);
1292        }
1293        StackMapFrame::AppendFrame {
1294            offset_delta,
1295            locals,
1296        } => {
1297            write_u1(out, 251 + locals.len() as u8);
1298            write_u2(out, *offset_delta);
1299            for local in locals {
1300                write_verification_type(out, local);
1301            }
1302        }
1303        StackMapFrame::FullFrame {
1304            offset_delta,
1305            locals,
1306            stack,
1307        } => {
1308            write_u1(out, 255);
1309            write_u2(out, *offset_delta);
1310            write_u2(out, locals.len() as u16);
1311            for local in locals {
1312                write_verification_type(out, local);
1313            }
1314            write_u2(out, stack.len() as u16);
1315            for value in stack {
1316                write_verification_type(out, value);
1317            }
1318        }
1319    }
1320}
1321
1322fn write_verification_type(out: &mut Vec<u8>, value: &VerificationTypeInfo) {
1323    match value {
1324        VerificationTypeInfo::Top => write_u1(out, 0),
1325        VerificationTypeInfo::Integer => write_u1(out, 1),
1326        VerificationTypeInfo::Float => write_u1(out, 2),
1327        VerificationTypeInfo::Double => write_u1(out, 3),
1328        VerificationTypeInfo::Long => write_u1(out, 4),
1329        VerificationTypeInfo::Null => write_u1(out, 5),
1330        VerificationTypeInfo::UninitializedThis => write_u1(out, 6),
1331        VerificationTypeInfo::Object { cpool_index } => {
1332            write_u1(out, 7);
1333            write_u2(out, *cpool_index);
1334        }
1335        VerificationTypeInfo::Uninitialized { offset } => {
1336            write_u1(out, 8);
1337            write_u2(out, *offset);
1338        }
1339    }
1340}
1341
1342fn collect_attribute_names(attributes: &[AttributeInfo], names: &mut Vec<String>) {
1343    for attr in attributes {
1344        match attr {
1345            AttributeInfo::Code(_) => names.push("Code".to_string()),
1346            AttributeInfo::ConstantValue { .. } => names.push("ConstantValue".to_string()),
1347            AttributeInfo::Exceptions { .. } => names.push("Exceptions".to_string()),
1348            AttributeInfo::SourceFile { .. } => names.push("SourceFile".to_string()),
1349            AttributeInfo::LineNumberTable { .. } => names.push("LineNumberTable".to_string()),
1350            AttributeInfo::LocalVariableTable { .. } => {
1351                names.push("LocalVariableTable".to_string())
1352            }
1353            AttributeInfo::Signature { .. } => names.push("Signature".to_string()),
1354            AttributeInfo::StackMapTable { .. } => names.push("StackMapTable".to_string()),
1355            AttributeInfo::Deprecated => names.push("Deprecated".to_string()),
1356            AttributeInfo::Synthetic => names.push("Synthetic".to_string()),
1357            AttributeInfo::InnerClasses { .. } => names.push("InnerClasses".to_string()),
1358            AttributeInfo::EnclosingMethod { .. } => names.push("EnclosingMethod".to_string()),
1359            AttributeInfo::BootstrapMethods { .. } => names.push("BootstrapMethods".to_string()),
1360            AttributeInfo::MethodParameters { .. } => names.push("MethodParameters".to_string()),
1361            AttributeInfo::Unknown { name, .. } => names.push(name.clone()),
1362        }
1363    }
1364}
1365
1366fn write_constant_pool(out: &mut Vec<u8>, cp: &[CpInfo]) -> Result<(), ClassWriteError> {
1367    write_u2(out, cp.len() as u16);
1368    for entry in cp.iter().skip(1) {
1369        match entry {
1370            CpInfo::Unusable => {}
1371            CpInfo::Utf8(value) => {
1372                let bytes = encode_modified_utf8(value);
1373                write_u1(out, 1);
1374                write_u2(out, bytes.len() as u16);
1375                out.extend_from_slice(&bytes);
1376            }
1377            CpInfo::Integer(value) => {
1378                write_u1(out, 3);
1379                write_u4(out, *value as u32);
1380            }
1381            CpInfo::Float(value) => {
1382                write_u1(out, 4);
1383                write_u4(out, value.to_bits());
1384            }
1385            CpInfo::Long(value) => {
1386                write_u1(out, 5);
1387                write_u8(out, *value as u64);
1388            }
1389            CpInfo::Double(value) => {
1390                write_u1(out, 6);
1391                write_u8(out, value.to_bits());
1392            }
1393            CpInfo::Class { name_index } => {
1394                write_u1(out, 7);
1395                write_u2(out, *name_index);
1396            }
1397            CpInfo::String { string_index } => {
1398                write_u1(out, 8);
1399                write_u2(out, *string_index);
1400            }
1401            CpInfo::Fieldref {
1402                class_index,
1403                name_and_type_index,
1404            } => {
1405                write_u1(out, 9);
1406                write_u2(out, *class_index);
1407                write_u2(out, *name_and_type_index);
1408            }
1409            CpInfo::Methodref {
1410                class_index,
1411                name_and_type_index,
1412            } => {
1413                write_u1(out, 10);
1414                write_u2(out, *class_index);
1415                write_u2(out, *name_and_type_index);
1416            }
1417            CpInfo::InterfaceMethodref {
1418                class_index,
1419                name_and_type_index,
1420            } => {
1421                write_u1(out, 11);
1422                write_u2(out, *class_index);
1423                write_u2(out, *name_and_type_index);
1424            }
1425            CpInfo::NameAndType {
1426                name_index,
1427                descriptor_index,
1428            } => {
1429                write_u1(out, 12);
1430                write_u2(out, *name_index);
1431                write_u2(out, *descriptor_index);
1432            }
1433            CpInfo::MethodHandle {
1434                reference_kind,
1435                reference_index,
1436            } => {
1437                write_u1(out, 15);
1438                write_u1(out, *reference_kind);
1439                write_u2(out, *reference_index);
1440            }
1441            CpInfo::MethodType { descriptor_index } => {
1442                write_u1(out, 16);
1443                write_u2(out, *descriptor_index);
1444            }
1445            CpInfo::Dynamic {
1446                bootstrap_method_attr_index,
1447                name_and_type_index,
1448            } => {
1449                write_u1(out, 17);
1450                write_u2(out, *bootstrap_method_attr_index);
1451                write_u2(out, *name_and_type_index);
1452            }
1453            CpInfo::InvokeDynamic {
1454                bootstrap_method_attr_index,
1455                name_and_type_index,
1456            } => {
1457                write_u1(out, 18);
1458                write_u2(out, *bootstrap_method_attr_index);
1459                write_u2(out, *name_and_type_index);
1460            }
1461            CpInfo::Module { name_index } => {
1462                write_u1(out, 19);
1463                write_u2(out, *name_index);
1464            }
1465            CpInfo::Package { name_index } => {
1466                write_u1(out, 20);
1467                write_u2(out, *name_index);
1468            }
1469        }
1470    }
1471    Ok(())
1472}
1473
1474fn encode_modified_utf8(value: &str) -> Vec<u8> {
1475    let mut out = Vec::new();
1476    for ch in value.chars() {
1477        let code = ch as u32;
1478        if code == 0 {
1479            out.push(0xC0);
1480            out.push(0x80);
1481        } else if code <= 0x7F {
1482            out.push(code as u8);
1483        } else if code <= 0x7FF {
1484            out.push((0xC0 | ((code >> 6) & 0x1F)) as u8);
1485            out.push((0x80 | (code & 0x3F)) as u8);
1486        } else if code <= 0xFFFF {
1487            out.push((0xE0 | ((code >> 12) & 0x0F)) as u8);
1488            out.push((0x80 | ((code >> 6) & 0x3F)) as u8);
1489            out.push((0x80 | (code & 0x3F)) as u8);
1490        } else {
1491            let u = code - 0x10000;
1492            let high = 0xD800 + ((u >> 10) & 0x3FF);
1493            let low = 0xDC00 + (u & 0x3FF);
1494            for cu in [high, low] {
1495                out.push((0xE0 | ((cu >> 12) & 0x0F)) as u8);
1496                out.push((0x80 | ((cu >> 6) & 0x3F)) as u8);
1497                out.push((0x80 | (cu & 0x3F)) as u8);
1498            }
1499        }
1500    }
1501    out
1502}
1503
1504fn ensure_utf8(cp: &mut Vec<CpInfo>, value: &str) -> u16 {
1505    if let Some(index) = cp_find_utf8(cp, value) {
1506        return index;
1507    }
1508    cp.push(CpInfo::Utf8(value.to_string()));
1509    (cp.len() - 1) as u16
1510}
1511
1512fn ensure_class(cp: &mut Vec<CpInfo>, name: &str) -> u16 {
1513    for (index, entry) in cp.iter().enumerate() {
1514        if let CpInfo::Class { name_index } = entry
1515            && let Some(CpInfo::Utf8(value)) = cp.get(*name_index as usize)
1516            && value == name
1517        {
1518            return index as u16;
1519        }
1520    }
1521    let name_index = ensure_utf8(cp, name);
1522    cp.push(CpInfo::Class { name_index });
1523    (cp.len() - 1) as u16
1524}
1525
1526fn cp_find_utf8(cp: &[CpInfo], value: &str) -> Option<u16> {
1527    for (index, entry) in cp.iter().enumerate() {
1528        if let CpInfo::Utf8(existing) = entry
1529            && existing == value
1530        {
1531            return Some(index as u16);
1532        }
1533    }
1534    None
1535}
1536
1537fn write_u1(out: &mut Vec<u8>, value: u8) {
1538    out.push(value);
1539}
1540
1541fn write_u2(out: &mut Vec<u8>, value: u16) {
1542    out.extend_from_slice(&value.to_be_bytes());
1543}
1544
1545fn write_u4(out: &mut Vec<u8>, value: u32) {
1546    out.extend_from_slice(&value.to_be_bytes());
1547}
1548
1549fn write_i1(out: &mut Vec<u8>, value: i8) {
1550    out.push(value as u8);
1551}
1552
1553fn write_i2(out: &mut Vec<u8>, value: i16) {
1554    out.extend_from_slice(&value.to_be_bytes());
1555}
1556
1557fn write_i4(out: &mut Vec<u8>, value: i32) {
1558    out.extend_from_slice(&value.to_be_bytes());
1559}
1560
1561fn write_i2_at(out: &mut [u8], pos: usize, value: i16) {
1562    let bytes = value.to_be_bytes();
1563    out[pos] = bytes[0];
1564    out[pos + 1] = bytes[1];
1565}
1566
1567fn write_i4_at(out: &mut [u8], pos: usize, value: i32) {
1568    let bytes = value.to_be_bytes();
1569    out[pos] = bytes[0];
1570    out[pos + 1] = bytes[1];
1571    out[pos + 2] = bytes[2];
1572    out[pos + 3] = bytes[3];
1573}
1574
1575fn write_switch_padding(out: &mut Vec<u8>, opcode_offset: usize) {
1576    let mut padding = (4 - ((opcode_offset + 1) % 4)) % 4;
1577    while padding > 0 {
1578        out.push(0);
1579        padding -= 1;
1580    }
1581}
1582
1583fn write_u8(out: &mut Vec<u8>, value: u64) {
1584    out.extend_from_slice(&value.to_be_bytes());
1585}
1586
1587#[derive(Debug, Clone, PartialEq, Eq)]
1588enum FrameType {
1589    Top,
1590    Integer,
1591    Float,
1592    Long,
1593    Double,
1594    Null,
1595    UninitializedThis,
1596    Object(String),
1597    Uninitialized(u16),
1598}
1599
1600fn compute_stack_map_table(
1601    method: &MethodNode,
1602    class_node: &ClassNode,
1603    code: &CodeAttribute,
1604    cp: &mut Vec<CpInfo>,
1605    max_locals: u16,
1606) -> Result<Vec<StackMapFrame>, ClassWriteError> {
1607    let insns = parse_instructions(&code.code)?;
1608    if insns.is_empty() {
1609        return Ok(Vec::new());
1610    }
1611
1612    let mut insn_index = std::collections::HashMap::new();
1613    for (index, insn) in insns.iter().enumerate() {
1614        insn_index.insert(insn.offset, index);
1615    }
1616
1617    let handlers = build_exception_handlers(code, cp)?;
1618    let handler_common = handler_common_types(&handlers);
1619    let mut frames: std::collections::HashMap<u16, FrameState> = std::collections::HashMap::new();
1620    let mut worklist = std::collections::VecDeque::new();
1621    let mut in_worklist = std::collections::HashSet::new();
1622
1623    let mut initial = initial_frame(method, class_node)?;
1624    pad_locals(&mut initial.locals, max_locals);
1625    frames.insert(0, initial.clone());
1626    worklist.push_back(0u16);
1627    in_worklist.insert(0u16);
1628
1629    let mut max_iterations = 0usize;
1630    while let Some(offset) = worklist.pop_front() {
1631        in_worklist.remove(&offset);
1632        max_iterations += 1;
1633        if max_iterations > 100000 {
1634            return Err(ClassWriteError::FrameComputation(
1635                "frame analysis exceeded iteration limit".to_string(),
1636            ));
1637        }
1638        let index = *insn_index.get(&offset).ok_or_else(|| {
1639            ClassWriteError::FrameComputation(format!("missing instruction at {offset}"))
1640        })?;
1641        let insn = &insns[index];
1642        let frame = frames
1643            .get(&offset)
1644            .ok_or_else(|| ClassWriteError::FrameComputation(format!("missing frame at {offset}")))?
1645            .clone();
1646        let insn1 = &insn;
1647        let out_frame = execute_instruction(insn1, &frame, class_node, cp)?;
1648
1649        for succ in instruction_successors(insn) {
1650            if let Some(next_frame) = merge_frame(&out_frame, frames.get(&succ)) {
1651                let changed = match frames.get(&succ) {
1652                    Some(existing) => existing != &next_frame,
1653                    None => true,
1654                };
1655                if changed {
1656                    frames.insert(succ, next_frame);
1657                    if in_worklist.insert(succ) {
1658                        worklist.push_back(succ);
1659                    }
1660                }
1661            }
1662        }
1663
1664        for handler in handlers.iter().filter(|item| item.covers(offset)) {
1665            let mut handler_frame = FrameState {
1666                locals: frame.locals.clone(),
1667                stack: Vec::new(),
1668            };
1669            let exception_type = handler_common
1670                .get(&handler.handler_pc)
1671                .cloned()
1672                .unwrap_or_else(|| handler.exception_type.clone());
1673            handler_frame.stack.push(exception_type);
1674            if let Some(next_frame) = merge_frame(&handler_frame, frames.get(&handler.handler_pc)) {
1675                let changed = match frames.get(&handler.handler_pc) {
1676                    Some(existing) => existing != &next_frame,
1677                    None => true,
1678                };
1679                if changed {
1680                    frames.insert(handler.handler_pc, next_frame);
1681                    if in_worklist.insert(handler.handler_pc) {
1682                        worklist.push_back(handler.handler_pc);
1683                    }
1684                }
1685            }
1686        }
1687    }
1688
1689    let mut frame_offsets: Vec<u16> = frames.keys().copied().collect();
1690    frame_offsets.sort_unstable();
1691    let mut result = Vec::new();
1692    let mut previous_offset: i32 = -1;
1693    for offset in frame_offsets {
1694        if offset == 0 {
1695            continue;
1696        }
1697        let frame = frames
1698            .get(&offset)
1699            .ok_or_else(|| ClassWriteError::FrameComputation(format!("missing frame at {offset}")))?
1700            .clone();
1701        let locals = compact_locals(&frame.locals);
1702        let stack = frame.stack;
1703        let offset_delta = (offset as i32 - previous_offset - 1) as u16;
1704        previous_offset = offset as i32;
1705        let locals_info = locals
1706            .iter()
1707            .map(|value| to_verification_type(value, cp))
1708            .collect();
1709        let stack_info = stack
1710            .iter()
1711            .map(|value| to_verification_type(value, cp))
1712            .collect();
1713        result.push(StackMapFrame::FullFrame {
1714            offset_delta,
1715            locals: locals_info,
1716            stack: stack_info,
1717        });
1718    }
1719
1720    Ok(result)
1721}
1722
1723#[derive(Debug, Clone, PartialEq, Eq)]
1724struct FrameState {
1725    locals: Vec<FrameType>,
1726    stack: Vec<FrameType>,
1727}
1728
1729fn merge_frame(frame: &FrameState, existing: Option<&FrameState>) -> Option<FrameState> {
1730    match existing {
1731        None => Some(frame.clone()),
1732        Some(other) => {
1733            let merged = FrameState {
1734                locals: merge_vec(&frame.locals, &other.locals),
1735                stack: merge_vec(&frame.stack, &other.stack),
1736            };
1737            if merged == *other { None } else { Some(merged) }
1738        }
1739    }
1740}
1741
1742fn merge_vec(a: &[FrameType], b: &[FrameType]) -> Vec<FrameType> {
1743    let len = a.len().max(b.len());
1744    let mut merged = Vec::with_capacity(len);
1745    for i in 0..len {
1746        let left = a.get(i).cloned().unwrap_or(FrameType::Top);
1747        let right = b.get(i).cloned().unwrap_or(FrameType::Top);
1748        merged.push(merge_type(&left, &right));
1749    }
1750    merged
1751}
1752
1753fn merge_type(a: &FrameType, b: &FrameType) -> FrameType {
1754    if a == b {
1755        return a.clone();
1756    }
1757    match (a, b) {
1758        (FrameType::Top, _) => FrameType::Top,
1759        (_, FrameType::Top) => FrameType::Top,
1760        (FrameType::Null, FrameType::Object(name)) | (FrameType::Object(name), FrameType::Null) => {
1761            FrameType::Object(name.clone())
1762        }
1763        (FrameType::Object(left), FrameType::Object(right)) => {
1764            FrameType::Object(common_superclass(left, right))
1765        }
1766        (FrameType::Object(_), FrameType::Uninitialized(_))
1767        | (FrameType::Uninitialized(_), FrameType::Object(_))
1768        | (FrameType::UninitializedThis, FrameType::Object(_))
1769        | (FrameType::Object(_), FrameType::UninitializedThis) => {
1770            FrameType::Object("java/lang/Object".to_string())
1771        }
1772        _ => FrameType::Top,
1773    }
1774}
1775
1776fn common_superclass(left: &str, right: &str) -> String {
1777    if left == right {
1778        return left.to_string();
1779    }
1780    if left.starts_with('[') || right.starts_with('[') {
1781        return "java/lang/Object".to_string();
1782    }
1783
1784    let mut ancestors = std::collections::HashSet::new();
1785    let mut current = left;
1786    ancestors.insert(current.to_string());
1787    while let Some(parent) = known_superclass(current) {
1788        if ancestors.insert(parent.to_string()) {
1789            current = parent;
1790        } else {
1791            break;
1792        }
1793    }
1794    ancestors.insert("java/lang/Object".to_string());
1795
1796    current = right;
1797    if ancestors.contains(current) {
1798        return current.to_string();
1799    }
1800    while let Some(parent) = known_superclass(current) {
1801        if ancestors.contains(parent) {
1802            return parent.to_string();
1803        }
1804        current = parent;
1805    }
1806    "java/lang/Object".to_string()
1807}
1808
1809fn known_superclass(name: &str) -> Option<&'static str> {
1810    match name {
1811        "java/lang/Throwable" => Some("java/lang/Object"),
1812        "java/lang/Exception" => Some("java/lang/Throwable"),
1813        "java/lang/RuntimeException" => Some("java/lang/Exception"),
1814        "java/lang/IllegalArgumentException" => Some("java/lang/RuntimeException"),
1815        "java/lang/IllegalStateException" => Some("java/lang/RuntimeException"),
1816        "java/security/GeneralSecurityException" => Some("java/lang/Exception"),
1817        "java/security/NoSuchAlgorithmException" => Some("java/security/GeneralSecurityException"),
1818        "java/security/InvalidKeyException" => Some("java/security/GeneralSecurityException"),
1819        "javax/crypto/NoSuchPaddingException" => Some("java/security/GeneralSecurityException"),
1820        "javax/crypto/IllegalBlockSizeException" => Some("java/security/GeneralSecurityException"),
1821        "javax/crypto/BadPaddingException" => Some("java/security/GeneralSecurityException"),
1822        _ => None,
1823    }
1824}
1825
1826fn pad_locals(locals: &mut Vec<FrameType>, max_locals: u16) {
1827    while locals.len() < max_locals as usize {
1828        locals.push(FrameType::Top);
1829    }
1830}
1831
1832fn compute_maxs(
1833    method: &MethodNode,
1834    class_node: &ClassNode,
1835    code: &CodeAttribute,
1836    cp: &[CpInfo],
1837) -> Result<(u16, u16), ClassWriteError> {
1838    let insns = parse_instructions(&code.code)?;
1839    if insns.is_empty() {
1840        let initial = initial_frame(method, class_node)?;
1841        return Ok((0, initial.locals.len() as u16));
1842    }
1843
1844    let mut insn_index = std::collections::HashMap::new();
1845    for (index, insn) in insns.iter().enumerate() {
1846        insn_index.insert(insn.offset, index);
1847    }
1848
1849    let handlers = build_exception_handlers(code, cp)?;
1850    let mut frames: std::collections::HashMap<u16, FrameState> = std::collections::HashMap::new();
1851    let mut worklist = std::collections::VecDeque::new();
1852    let mut in_worklist = std::collections::HashSet::new();
1853
1854    let initial = initial_frame(method, class_node)?;
1855    frames.insert(0, initial.clone());
1856    worklist.push_back(0u16);
1857    in_worklist.insert(0u16);
1858
1859    let mut max_stack = initial.stack.len();
1860    let mut max_locals = initial.locals.len();
1861    let mut max_iterations = 0usize;
1862    let mut offset_hits: std::collections::HashMap<u16, u32> = std::collections::HashMap::new();
1863    while let Some(offset) = worklist.pop_front() {
1864        in_worklist.remove(&offset);
1865        max_iterations += 1;
1866        *offset_hits.entry(offset).or_insert(0) += 1;
1867        if max_iterations > 100000 {
1868            return Err(ClassWriteError::FrameComputation(
1869                "frame analysis exceeded iteration limit".to_string(),
1870            ));
1871        }
1872        let index = *insn_index.get(&offset).ok_or_else(|| {
1873            ClassWriteError::FrameComputation(format!("missing instruction at {offset}"))
1874        })?;
1875        let insn = &insns[index];
1876        let frame = frames.get(&offset).cloned().ok_or_else(|| {
1877            ClassWriteError::FrameComputation(format!("missing frame at {offset}"))
1878        })?;
1879        max_stack = max_stack.max(stack_slots(&frame.stack));
1880        max_locals = max_locals.max(frame.locals.len());
1881
1882        let out_frame = execute_instruction(insn, &frame, class_node, cp)?;
1883        max_stack = max_stack.max(stack_slots(&out_frame.stack));
1884        max_locals = max_locals.max(out_frame.locals.len());
1885
1886        for succ in instruction_successors(insn) {
1887            if let Some(next_frame) = merge_frame(&out_frame, frames.get(&succ)) {
1888                let changed = match frames.get(&succ) {
1889                    Some(existing) => existing != &next_frame,
1890                    None => true,
1891                };
1892                if changed {
1893                    frames.insert(succ, next_frame);
1894                    if in_worklist.insert(succ) {
1895                        worklist.push_back(succ);
1896                    }
1897                }
1898            }
1899        }
1900
1901        for handler in handlers.iter().filter(|item| item.covers(offset)) {
1902            let mut handler_frame = FrameState {
1903                locals: frame.locals.clone(),
1904                stack: Vec::new(),
1905            };
1906            handler_frame.stack.push(handler.exception_type.clone());
1907            max_stack = max_stack.max(stack_slots(&handler_frame.stack));
1908            max_locals = max_locals.max(handler_frame.locals.len());
1909            if let Some(next_frame) = merge_frame(&handler_frame, frames.get(&handler.handler_pc)) {
1910                let changed = match frames.get(&handler.handler_pc) {
1911                    Some(existing) => existing != &next_frame,
1912                    None => true,
1913                };
1914                if changed {
1915                    frames.insert(handler.handler_pc, next_frame);
1916                    if in_worklist.insert(handler.handler_pc) {
1917                        worklist.push_back(handler.handler_pc);
1918                    }
1919                }
1920            }
1921        }
1922    }
1923
1924    Ok((max_stack as u16, max_locals as u16))
1925}
1926
1927fn stack_slots(stack: &[FrameType]) -> usize {
1928    let mut slots = 0usize;
1929    for value in stack {
1930        slots += if is_category2(value) { 2 } else { 1 };
1931    }
1932    slots
1933}
1934
1935fn compact_locals(locals: &[FrameType]) -> Vec<FrameType> {
1936    let mut out = Vec::new();
1937    let mut i = 0usize;
1938    while i < locals.len() {
1939        match locals[i] {
1940            FrameType::Top => {
1941                if i > 0 && matches!(locals[i - 1], FrameType::Long | FrameType::Double) {
1942                    i += 1;
1943                    continue;
1944                }
1945                out.push(FrameType::Top);
1946            }
1947            FrameType::Long | FrameType::Double => {
1948                out.push(locals[i].clone());
1949                if i + 1 < locals.len() && matches!(locals[i + 1], FrameType::Top) {
1950                    i += 1;
1951                }
1952            }
1953            _ => out.push(locals[i].clone()),
1954        }
1955        i += 1;
1956    }
1957
1958    while matches!(out.last(), Some(FrameType::Top)) {
1959        out.pop();
1960    }
1961    out
1962}
1963
1964fn to_verification_type(value: &FrameType, cp: &mut Vec<CpInfo>) -> VerificationTypeInfo {
1965    match value {
1966        FrameType::Top => VerificationTypeInfo::Top,
1967        FrameType::Integer => VerificationTypeInfo::Integer,
1968        FrameType::Float => VerificationTypeInfo::Float,
1969        FrameType::Long => VerificationTypeInfo::Long,
1970        FrameType::Double => VerificationTypeInfo::Double,
1971        FrameType::Null => VerificationTypeInfo::Null,
1972        FrameType::UninitializedThis => VerificationTypeInfo::UninitializedThis,
1973        FrameType::Uninitialized(offset) => VerificationTypeInfo::Uninitialized { offset: *offset },
1974        FrameType::Object(name) => {
1975            let index = ensure_class(cp, name);
1976            VerificationTypeInfo::Object { cpool_index: index }
1977        }
1978    }
1979}
1980
1981fn initial_frame(
1982    method: &MethodNode,
1983    class_node: &ClassNode,
1984) -> Result<FrameState, ClassWriteError> {
1985    let mut locals = Vec::new();
1986    let is_static = method.access_flags & constants::ACC_STATIC != 0;
1987    if !is_static {
1988        if method.name == "<init>" {
1989            locals.push(FrameType::UninitializedThis);
1990        } else {
1991            locals.push(FrameType::Object(class_node.name.clone()));
1992        }
1993    }
1994    let (params, _) = parse_method_descriptor(&method.descriptor)?;
1995    for param in params {
1996        push_local_type(&mut locals, param);
1997    }
1998    Ok(FrameState {
1999        locals,
2000        stack: Vec::new(),
2001    })
2002}
2003
2004fn push_local_type(locals: &mut Vec<FrameType>, ty: FieldType) {
2005    match ty {
2006        FieldType::Long => {
2007            locals.push(FrameType::Long);
2008            locals.push(FrameType::Top);
2009        }
2010        FieldType::Double => {
2011            locals.push(FrameType::Double);
2012            locals.push(FrameType::Top);
2013        }
2014        FieldType::Float => locals.push(FrameType::Float),
2015        FieldType::Boolean
2016        | FieldType::Byte
2017        | FieldType::Char
2018        | FieldType::Short
2019        | FieldType::Int => locals.push(FrameType::Integer),
2020        FieldType::Object(name) => locals.push(FrameType::Object(name)),
2021        FieldType::Array(desc) => locals.push(FrameType::Object(desc)),
2022        FieldType::Void => {}
2023    }
2024}
2025
2026#[derive(Debug, Clone)]
2027struct ExceptionHandlerInfo {
2028    start_pc: u16,
2029    end_pc: u16,
2030    handler_pc: u16,
2031    exception_type: FrameType,
2032}
2033
2034impl ExceptionHandlerInfo {
2035    fn covers(&self, offset: u16) -> bool {
2036        offset >= self.start_pc && offset < self.end_pc
2037    }
2038}
2039
2040fn build_exception_handlers(
2041    code: &CodeAttribute,
2042    cp: &[CpInfo],
2043) -> Result<Vec<ExceptionHandlerInfo>, ClassWriteError> {
2044    let mut handlers = Vec::new();
2045    for entry in &code.exception_table {
2046        let exception_type = if entry.catch_type == 0 {
2047            FrameType::Object("java/lang/Throwable".to_string())
2048        } else {
2049            let class_name = cp_class_name(cp, entry.catch_type)?;
2050            FrameType::Object(class_name.to_string())
2051        };
2052        handlers.push(ExceptionHandlerInfo {
2053            start_pc: entry.start_pc,
2054            end_pc: entry.end_pc,
2055            handler_pc: entry.handler_pc,
2056            exception_type,
2057        });
2058    }
2059    Ok(handlers)
2060}
2061
2062fn handler_common_types(
2063    handlers: &[ExceptionHandlerInfo],
2064) -> std::collections::HashMap<u16, FrameType> {
2065    let mut map: std::collections::HashMap<u16, FrameType> = std::collections::HashMap::new();
2066    for handler in handlers {
2067        map.entry(handler.handler_pc)
2068            .and_modify(|existing| {
2069                *existing = merge_exception_type(existing, &handler.exception_type);
2070            })
2071            .or_insert_with(|| handler.exception_type.clone());
2072    }
2073    map
2074}
2075
2076fn merge_exception_type(left: &FrameType, right: &FrameType) -> FrameType {
2077    match (left, right) {
2078        (FrameType::Object(l), FrameType::Object(r)) => FrameType::Object(common_superclass(l, r)),
2079        _ if left == right => left.clone(),
2080        _ => FrameType::Object("java/lang/Object".to_string()),
2081    }
2082}
2083
2084fn dump_frame_debug(
2085    method: &MethodNode,
2086    label: &str,
2087    iterations: usize,
2088    hits: &std::collections::HashMap<u16, u32>,
2089) {
2090    let mut entries: Vec<(u16, u32)> = hits.iter().map(|(k, v)| (*k, *v)).collect();
2091    entries.sort_by(|a, b| b.1.cmp(&a.1));
2092    let top = entries.into_iter().take(10).collect::<Vec<_>>();
2093    eprintln!(
2094        "[frame-debug] method={}{} label={} iterations={} top_offsets={:?}",
2095        method.name, method.descriptor, label, iterations, top
2096    );
2097}
2098
2099#[derive(Debug, Clone)]
2100struct ParsedInstruction {
2101    offset: u16,
2102    opcode: u8,
2103    operand: Operand,
2104}
2105
2106#[derive(Debug, Clone)]
2107enum Operand {
2108    None,
2109    I1(i8),
2110    I2(i16),
2111    I4(i32),
2112    U1(u8),
2113    U2(u16),
2114    U4(u32),
2115    Jump(i16),
2116    JumpWide(i32),
2117    TableSwitch {
2118        default_offset: i32,
2119        low: i32,
2120        high: i32,
2121        offsets: Vec<i32>,
2122    },
2123    LookupSwitch {
2124        default_offset: i32,
2125        pairs: Vec<(i32, i32)>,
2126    },
2127    Iinc {
2128        index: u16,
2129        increment: i16,
2130    },
2131    InvokeInterface {
2132        index: u16,
2133        count: u8,
2134    },
2135    InvokeDynamic {
2136        index: u16,
2137    },
2138    MultiANewArray {
2139        index: u16,
2140        dims: u8,
2141    },
2142    Wide {
2143        opcode: u8,
2144        index: u16,
2145        increment: Option<i16>,
2146    },
2147}
2148
2149fn parse_instructions(code: &[u8]) -> Result<Vec<ParsedInstruction>, ClassWriteError> {
2150    let mut insns = Vec::new();
2151    let mut pos = 0usize;
2152    while pos < code.len() {
2153        let offset = pos as u16;
2154        let opcode = code[pos];
2155        pos += 1;
2156        let operand = match opcode {
2157            opcodes::BIPUSH => {
2158                let value = read_i1(code, &mut pos)?;
2159                Operand::I1(value)
2160            }
2161            opcodes::SIPUSH => Operand::I2(read_i2(code, &mut pos)?),
2162            opcodes::LDC => Operand::U1(read_u1(code, &mut pos)?),
2163            opcodes::LDC_W | opcodes::LDC2_W => Operand::U2(read_u2(code, &mut pos)?),
2164            opcodes::ILOAD..=opcodes::ALOAD | opcodes::ISTORE..=opcodes::ASTORE | opcodes::RET => {
2165                Operand::U1(read_u1(code, &mut pos)?)
2166            }
2167            opcodes::IINC => {
2168                let index = read_u1(code, &mut pos)? as u16;
2169                let inc = read_i1(code, &mut pos)? as i16;
2170                Operand::Iinc {
2171                    index,
2172                    increment: inc,
2173                }
2174            }
2175            opcodes::IFEQ..=opcodes::JSR | opcodes::IFNULL | opcodes::IFNONNULL => {
2176                Operand::Jump(read_i2(code, &mut pos)?)
2177            }
2178            opcodes::GOTO_W | opcodes::JSR_W => Operand::JumpWide(read_i4(code, &mut pos)?),
2179            opcodes::TABLESWITCH => {
2180                let padding = (4 - (pos % 4)) % 4;
2181                pos += padding;
2182                let default_offset = read_i4(code, &mut pos)?;
2183                let low = read_i4(code, &mut pos)?;
2184                let high = read_i4(code, &mut pos)?;
2185                let count = if high < low {
2186                    0
2187                } else {
2188                    (high - low + 1) as usize
2189                };
2190                let mut offsets = Vec::with_capacity(count);
2191                for _ in 0..count {
2192                    offsets.push(read_i4(code, &mut pos)?);
2193                }
2194                Operand::TableSwitch {
2195                    default_offset,
2196                    low,
2197                    high,
2198                    offsets,
2199                }
2200            }
2201            opcodes::LOOKUPSWITCH => {
2202                let padding = (4 - (pos % 4)) % 4;
2203                pos += padding;
2204                let default_offset = read_i4(code, &mut pos)?;
2205                let npairs = read_i4(code, &mut pos)? as usize;
2206                let mut pairs = Vec::with_capacity(npairs);
2207                for _ in 0..npairs {
2208                    let key = read_i4(code, &mut pos)?;
2209                    let value = read_i4(code, &mut pos)?;
2210                    pairs.push((key, value));
2211                }
2212                Operand::LookupSwitch {
2213                    default_offset,
2214                    pairs,
2215                }
2216            }
2217            opcodes::GETSTATIC..=opcodes::INVOKESTATIC
2218            | opcodes::NEW
2219            | opcodes::ANEWARRAY
2220            | opcodes::CHECKCAST
2221            | opcodes::INSTANCEOF => Operand::U2(read_u2(code, &mut pos)?),
2222            opcodes::INVOKEINTERFACE => {
2223                let index = read_u2(code, &mut pos)?;
2224                let count = read_u1(code, &mut pos)?;
2225                let _ = read_u1(code, &mut pos)?;
2226                Operand::InvokeInterface { index, count }
2227            }
2228            opcodes::INVOKEDYNAMIC => {
2229                let index = read_u2(code, &mut pos)?;
2230                let _ = read_u2(code, &mut pos)?;
2231                Operand::InvokeDynamic { index }
2232            }
2233            opcodes::NEWARRAY => Operand::U1(read_u1(code, &mut pos)?),
2234            opcodes::WIDE => {
2235                let wide_opcode = read_u1(code, &mut pos)?;
2236                match wide_opcode {
2237                    opcodes::ILOAD..=opcodes::ALOAD
2238                    | opcodes::ISTORE..=opcodes::ASTORE
2239                    | opcodes::RET => {
2240                        let index = read_u2(code, &mut pos)?;
2241                        Operand::Wide {
2242                            opcode: wide_opcode,
2243                            index,
2244                            increment: None,
2245                        }
2246                    }
2247                    opcodes::IINC => {
2248                        let index = read_u2(code, &mut pos)?;
2249                        let increment = read_i2(code, &mut pos)?;
2250                        Operand::Wide {
2251                            opcode: wide_opcode,
2252                            index,
2253                            increment: Some(increment),
2254                        }
2255                    }
2256                    _ => {
2257                        return Err(ClassWriteError::InvalidOpcode {
2258                            opcode: wide_opcode,
2259                            offset: pos - 1,
2260                        });
2261                    }
2262                }
2263            }
2264            opcodes::MULTIANEWARRAY => {
2265                let index = read_u2(code, &mut pos)?;
2266                let dims = read_u1(code, &mut pos)?;
2267                Operand::MultiANewArray { index, dims }
2268            }
2269            _ => Operand::None,
2270        };
2271        insns.push(ParsedInstruction {
2272            offset,
2273            opcode,
2274            operand,
2275        });
2276    }
2277    Ok(insns)
2278}
2279
2280fn instruction_successors(insn: &ParsedInstruction) -> Vec<u16> {
2281    let mut successors = Vec::new();
2282    let next_offset = insn.offset.saturating_add(instruction_length(insn) as u16);
2283    match insn.opcode {
2284        opcodes::GOTO | opcodes::GOTO_W => {
2285            if let Some(target) = jump_target(insn) {
2286                successors.push(target);
2287            }
2288        }
2289        opcodes::JSR | opcodes::JSR_W => {
2290            if let Some(target) = jump_target(insn) {
2291                successors.push(target);
2292            }
2293            successors.push(next_offset);
2294        }
2295        opcodes::IFEQ..=opcodes::IF_ACMPNE | opcodes::IFNULL | opcodes::IFNONNULL => {
2296            if let Some(target) = jump_target(insn) {
2297                successors.push(target);
2298            }
2299            successors.push(next_offset);
2300        }
2301        opcodes::TABLESWITCH => {
2302            if let Operand::TableSwitch {
2303                default_offset,
2304                offsets,
2305                ..
2306            } = &insn.operand
2307            {
2308                successors.push((insn.offset as i32 + default_offset) as u16);
2309                for offset in offsets {
2310                    successors.push((insn.offset as i32 + *offset) as u16);
2311                }
2312            }
2313        }
2314        opcodes::LOOKUPSWITCH => {
2315            if let Operand::LookupSwitch {
2316                default_offset,
2317                pairs,
2318            } = &insn.operand
2319            {
2320                successors.push((insn.offset as i32 + default_offset) as u16);
2321                for (_, offset) in pairs {
2322                    successors.push((insn.offset as i32 + *offset) as u16);
2323                }
2324            }
2325        }
2326        opcodes::IRETURN..=opcodes::RETURN | opcodes::ATHROW => {}
2327        opcodes::MONITORENTER | opcodes::MONITOREXIT => {
2328            successors.push(next_offset);
2329        }
2330        _ => {
2331            if next_offset != insn.offset {
2332                successors.push(next_offset);
2333            }
2334        }
2335    }
2336    successors
2337}
2338
2339fn jump_target(insn: &ParsedInstruction) -> Option<u16> {
2340    match insn.operand {
2341        Operand::Jump(offset) => Some((insn.offset as i32 + offset as i32) as u16),
2342        Operand::JumpWide(offset) => Some((insn.offset as i32 + offset) as u16),
2343        _ => None,
2344    }
2345}
2346
2347fn instruction_length(insn: &ParsedInstruction) -> usize {
2348    match &insn.operand {
2349        Operand::None => 1,
2350        Operand::I1(_) | Operand::U1(_) => 2,
2351        Operand::I2(_) | Operand::U2(_) | Operand::Jump(_) => 3,
2352        Operand::I4(_) | Operand::U4(_) | Operand::JumpWide(_) => 5,
2353        Operand::Iinc { .. } => 3,
2354        Operand::InvokeInterface { .. } => 5,
2355        Operand::InvokeDynamic { .. } => 5,
2356        Operand::MultiANewArray { .. } => 4,
2357        Operand::Wide {
2358            opcode, increment, ..
2359        } => {
2360            if *opcode == opcodes::IINC && increment.is_some() {
2361                6
2362            } else {
2363                4
2364            }
2365        }
2366        Operand::TableSwitch { offsets, .. } => {
2367            1 + switch_padding(insn.offset) + 12 + offsets.len() * 4
2368        }
2369        Operand::LookupSwitch { pairs, .. } => {
2370            1 + switch_padding(insn.offset) + 8 + pairs.len() * 8
2371        }
2372    }
2373}
2374
2375fn switch_padding(offset: u16) -> usize {
2376    let pos = (offset as usize + 1) % 4;
2377    (4 - pos) % 4
2378}
2379
2380fn execute_instruction(
2381    insn: &ParsedInstruction,
2382    frame: &FrameState,
2383    class_node: &ClassNode,
2384    cp: &[CpInfo],
2385) -> Result<FrameState, ClassWriteError> {
2386    let mut locals = frame.locals.clone();
2387    let mut stack = frame.stack.clone();
2388
2389    let pop = |stack: &mut Vec<FrameType>| {
2390        stack.pop().ok_or_else(|| {
2391            ClassWriteError::FrameComputation(format!("stack underflow at {}", insn.offset))
2392        })
2393    };
2394
2395    match insn.opcode {
2396        opcodes::NOP => {}
2397        opcodes::ACONST_NULL => stack.push(FrameType::Null),
2398        opcodes::ICONST_M1..=opcodes::ICONST_5 => stack.push(FrameType::Integer),
2399        opcodes::LCONST_0 | opcodes::LCONST_1 => stack.push(FrameType::Long),
2400        opcodes::FCONST_0..=opcodes::FCONST_2 => stack.push(FrameType::Float),
2401        opcodes::DCONST_0 | opcodes::DCONST_1 => stack.push(FrameType::Double),
2402        opcodes::BIPUSH => stack.push(FrameType::Integer),
2403        opcodes::SIPUSH => stack.push(FrameType::Integer),
2404        opcodes::LDC..=opcodes::LDC2_W => {
2405            let ty = ldc_type(insn, cp)?;
2406            stack.push(ty);
2407        }
2408        opcodes::ILOAD..=opcodes::ALOAD => {
2409            let index = var_index(insn)?;
2410            if let Some(value) = locals.get(index as usize) {
2411                stack.push(value.clone());
2412            } else {
2413                stack.push(FrameType::Top);
2414            }
2415        }
2416        opcodes::ILOAD_0..=opcodes::ILOAD_3 => stack.push(load_local(
2417            &locals,
2418            (insn.opcode - opcodes::ILOAD_0) as u16,
2419            FrameType::Integer,
2420        )),
2421        opcodes::LLOAD_0..=opcodes::LLOAD_3 => stack.push(load_local(
2422            &locals,
2423            (insn.opcode - opcodes::LLOAD_0) as u16,
2424            FrameType::Long,
2425        )),
2426        opcodes::FLOAD_0..=opcodes::FLOAD_3 => stack.push(load_local(
2427            &locals,
2428            (insn.opcode - opcodes::FLOAD_0) as u16,
2429            FrameType::Float,
2430        )),
2431        opcodes::DLOAD_0..=opcodes::DLOAD_3 => stack.push(load_local(
2432            &locals,
2433            (insn.opcode - opcodes::DLOAD_0) as u16,
2434            FrameType::Double,
2435        )),
2436        opcodes::ALOAD_0..=opcodes::ALOAD_3 => stack.push(load_local(
2437            &locals,
2438            (insn.opcode - opcodes::ALOAD_0) as u16,
2439            FrameType::Object(class_node.name.clone()),
2440        )),
2441        opcodes::IALOAD..=opcodes::SALOAD => {
2442            pop(&mut stack)?;
2443            let array_ref = pop(&mut stack)?; //fixed: array -> java/lang/Object.
2444            let ty = match insn.opcode {
2445                opcodes::IALOAD => FrameType::Integer,
2446                opcodes::LALOAD => FrameType::Long,
2447                opcodes::FALOAD => FrameType::Float,
2448                opcodes::DALOAD => FrameType::Double,
2449                opcodes::AALOAD => array_element_type(&array_ref)
2450                    .unwrap_or_else(|| FrameType::Object("java/lang/Object".to_string())),
2451                opcodes::BALOAD..=opcodes::SALOAD => FrameType::Integer,
2452                _ => FrameType::Top,
2453            };
2454            stack.push(ty);
2455        }
2456        opcodes::ISTORE..=opcodes::ASTORE => {
2457            let index = var_index(insn)?;
2458            let value = pop(&mut stack)?;
2459            store_local(&mut locals, index, value);
2460        }
2461        opcodes::ISTORE_0..=opcodes::ISTORE_3 => {
2462            let value = pop(&mut stack)?;
2463            store_local(&mut locals, (insn.opcode - opcodes::ISTORE_0) as u16, value);
2464        }
2465        opcodes::LSTORE_0..=opcodes::LSTORE_3 => {
2466            let value = pop(&mut stack)?;
2467            store_local(&mut locals, (insn.opcode - opcodes::LSTORE_0) as u16, value);
2468        }
2469        opcodes::FSTORE_0..=opcodes::FSTORE_3 => {
2470            let value = pop(&mut stack)?;
2471            store_local(&mut locals, (insn.opcode - opcodes::FSTORE_0) as u16, value);
2472        }
2473        opcodes::DSTORE_0..=opcodes::DSTORE_3 => {
2474            let value = pop(&mut stack)?;
2475            store_local(&mut locals, (insn.opcode - opcodes::DSTORE_0) as u16, value);
2476        }
2477        opcodes::ASTORE_0..=opcodes::ASTORE_3 => {
2478            let value = pop(&mut stack)?;
2479            store_local(&mut locals, (insn.opcode - opcodes::ASTORE_0) as u16, value);
2480        }
2481        opcodes::IASTORE..=opcodes::SASTORE => {
2482            pop(&mut stack)?;
2483            pop(&mut stack)?;
2484            pop(&mut stack)?;
2485        }
2486        opcodes::POP => {
2487            pop(&mut stack)?;
2488        }
2489        opcodes::POP2 => {
2490            let v1 = pop(&mut stack)?;
2491            if is_category2(&v1) {
2492                // Form 2: ..., value2(cat2) -> ...
2493                // already popped
2494            } else {
2495                let v2 = pop(&mut stack)?;
2496                if is_category2(&v2) {
2497                    return Err(ClassWriteError::FrameComputation(
2498                        "pop2 invalid".to_string(),
2499                    ));
2500                }
2501            }
2502        }
2503        opcodes::DUP => {
2504            let v1 = pop(&mut stack)?;
2505            if is_category2(&v1) {
2506                return Err(ClassWriteError::FrameComputation(
2507                    "dup category2".to_string(),
2508                ));
2509            }
2510            stack.push(v1.clone());
2511            stack.push(v1);
2512        }
2513        opcodes::DUP_X1 => {
2514            let v1 = pop(&mut stack)?;
2515            let v2 = pop(&mut stack)?;
2516            if is_category2(&v1) || is_category2(&v2) {
2517                return Err(ClassWriteError::FrameComputation("dup_x1".to_string()));
2518            }
2519            stack.push(v1.clone());
2520            stack.push(v2);
2521            stack.push(v1);
2522        }
2523        opcodes::DUP_X2 => {
2524            let v1 = pop(&mut stack)?;
2525            if is_category2(&v1) {
2526                return Err(ClassWriteError::FrameComputation("dup_x2".to_string()));
2527            }
2528            let v2 = pop(&mut stack)?;
2529            if is_category2(&v2) {
2530                // Form 2: ..., v2(cat2), v1(cat1) -> ..., v1, v2, v1
2531                stack.push(v1.clone());
2532                stack.push(v2);
2533                stack.push(v1);
2534            } else {
2535                // Form 1/3: ..., v3(cat1|cat2), v2(cat1), v1(cat1) -> ..., v1, v3, v2, v1
2536                let v3 = pop(&mut stack)?;
2537                stack.push(v1.clone());
2538                stack.push(v3);
2539                stack.push(v2);
2540                stack.push(v1);
2541            }
2542        }
2543        opcodes::DUP2 => {
2544            let v1 = pop(&mut stack)?;
2545            if is_category2(&v1) {
2546                stack.push(v1.clone());
2547                stack.push(v1);
2548            } else {
2549                let v2 = pop(&mut stack)?;
2550                if is_category2(&v2) {
2551                    return Err(ClassWriteError::FrameComputation("dup2".to_string()));
2552                }
2553                stack.push(v2.clone());
2554                stack.push(v1.clone());
2555                stack.push(v2);
2556                stack.push(v1);
2557            }
2558        }
2559        opcodes::DUP2_X1 => {
2560            let v1 = pop(&mut stack)?;
2561            if is_category2(&v1) {
2562                let v2 = pop(&mut stack)?;
2563                stack.push(v1.clone());
2564                stack.push(v2);
2565                stack.push(v1);
2566            } else {
2567                let v2 = pop(&mut stack)?;
2568                let v3 = pop(&mut stack)?;
2569                stack.push(v2.clone());
2570                stack.push(v1.clone());
2571                stack.push(v3);
2572                stack.push(v2);
2573                stack.push(v1);
2574            }
2575        }
2576        opcodes::DUP2_X2 => {
2577            let v1 = pop(&mut stack)?;
2578            if is_category2(&v1) {
2579                let v2 = pop(&mut stack)?;
2580                let v3 = pop(&mut stack)?;
2581                stack.push(v1.clone());
2582                stack.push(v3);
2583                stack.push(v2);
2584                stack.push(v1);
2585            } else {
2586                let v2 = pop(&mut stack)?;
2587                let v3 = pop(&mut stack)?;
2588                let v4 = pop(&mut stack)?;
2589                stack.push(v2.clone());
2590                stack.push(v1.clone());
2591                stack.push(v4);
2592                stack.push(v3);
2593                stack.push(v2);
2594                stack.push(v1);
2595            }
2596        }
2597        opcodes::SWAP => {
2598            let v1 = pop(&mut stack)?;
2599            let v2 = pop(&mut stack)?;
2600            if is_category2(&v1) || is_category2(&v2) {
2601                return Err(ClassWriteError::FrameComputation("swap".to_string()));
2602            }
2603            stack.push(v1);
2604            stack.push(v2);
2605        }
2606        opcodes::IADD
2607        | opcodes::ISUB
2608        | opcodes::IMUL
2609        | opcodes::IDIV
2610        | opcodes::IREM
2611        | opcodes::ISHL
2612        | opcodes::ISHR
2613        | opcodes::IUSHR
2614        | opcodes::IAND
2615        | opcodes::IOR
2616        | opcodes::IXOR => {
2617            pop(&mut stack)?;
2618            pop(&mut stack)?;
2619            stack.push(FrameType::Integer);
2620        }
2621        opcodes::LADD
2622        | opcodes::LSUB
2623        | opcodes::LMUL
2624        | opcodes::LDIV
2625        | opcodes::LREM
2626        | opcodes::LSHL
2627        | opcodes::LSHR
2628        | opcodes::LUSHR
2629        | opcodes::LAND
2630        | opcodes::LOR
2631        | opcodes::LXOR => {
2632            pop(&mut stack)?;
2633            pop(&mut stack)?;
2634            stack.push(FrameType::Long);
2635        }
2636        opcodes::FADD | opcodes::FSUB | opcodes::FMUL | opcodes::FDIV | opcodes::FREM => {
2637            pop(&mut stack)?;
2638            pop(&mut stack)?;
2639            stack.push(FrameType::Float);
2640        }
2641        opcodes::DADD | opcodes::DSUB | opcodes::DMUL | opcodes::DDIV | opcodes::DREM => {
2642            pop(&mut stack)?;
2643            pop(&mut stack)?;
2644            stack.push(FrameType::Double);
2645        }
2646        opcodes::INEG => {
2647            pop(&mut stack)?;
2648            stack.push(FrameType::Integer);
2649        }
2650        opcodes::LNEG => {
2651            pop(&mut stack)?;
2652            stack.push(FrameType::Long);
2653        }
2654        opcodes::FNEG => {
2655            pop(&mut stack)?;
2656            stack.push(FrameType::Float);
2657        }
2658        opcodes::DNEG => {
2659            pop(&mut stack)?;
2660            stack.push(FrameType::Double);
2661        }
2662        opcodes::IINC => {}
2663        opcodes::I2L => {
2664            pop(&mut stack)?;
2665            stack.push(FrameType::Long);
2666        }
2667        opcodes::I2F => {
2668            pop(&mut stack)?;
2669            stack.push(FrameType::Float);
2670        }
2671        opcodes::I2D => {
2672            pop(&mut stack)?;
2673            stack.push(FrameType::Double);
2674        }
2675        opcodes::L2I => {
2676            pop(&mut stack)?;
2677            stack.push(FrameType::Integer);
2678        }
2679        opcodes::L2F => {
2680            pop(&mut stack)?;
2681            stack.push(FrameType::Float);
2682        }
2683        opcodes::L2D => {
2684            pop(&mut stack)?;
2685            stack.push(FrameType::Double);
2686        }
2687        opcodes::F2I => {
2688            pop(&mut stack)?;
2689            stack.push(FrameType::Integer);
2690        }
2691        opcodes::F2L => {
2692            pop(&mut stack)?;
2693            stack.push(FrameType::Long);
2694        }
2695        opcodes::F2D => {
2696            pop(&mut stack)?;
2697            stack.push(FrameType::Double);
2698        }
2699        opcodes::D2I => {
2700            pop(&mut stack)?;
2701            stack.push(FrameType::Integer);
2702        }
2703        opcodes::D2L => {
2704            pop(&mut stack)?;
2705            stack.push(FrameType::Long);
2706        }
2707        opcodes::D2F => {
2708            pop(&mut stack)?;
2709            stack.push(FrameType::Float);
2710        }
2711        opcodes::I2B..=opcodes::I2S => {
2712            pop(&mut stack)?;
2713            stack.push(FrameType::Integer);
2714        }
2715        opcodes::LCMP..=opcodes::DCMPG => {
2716            pop(&mut stack)?;
2717            pop(&mut stack)?;
2718            stack.push(FrameType::Integer);
2719        }
2720        opcodes::IFEQ..=opcodes::IFLE | opcodes::IFNULL | opcodes::IFNONNULL => {
2721            pop(&mut stack)?;
2722        }
2723        opcodes::IF_ICMPEQ..=opcodes::IF_ACMPNE => {
2724            pop(&mut stack)?;
2725            pop(&mut stack)?;
2726        }
2727        opcodes::GOTO | opcodes::GOTO_W => {}
2728        opcodes::JSR | opcodes::RET | opcodes::JSR_W => {
2729            return Err(ClassWriteError::FrameComputation(format!(
2730                "jsr/ret not supported at {}",
2731                insn.offset
2732            )));
2733        }
2734        opcodes::TABLESWITCH | opcodes::LOOKUPSWITCH => {
2735            pop(&mut stack)?;
2736        }
2737        opcodes::IRETURN => {
2738            pop(&mut stack)?;
2739        }
2740        opcodes::LRETURN => {
2741            pop(&mut stack)?;
2742        }
2743        opcodes::FRETURN => {
2744            pop(&mut stack)?;
2745        }
2746        opcodes::DRETURN => {
2747            pop(&mut stack)?;
2748        }
2749        opcodes::ARETURN => {
2750            pop(&mut stack)?;
2751        }
2752        opcodes::RETURN => {}
2753        opcodes::GETSTATIC => {
2754            let ty = field_type(insn, cp)?;
2755            stack.push(ty);
2756        }
2757        opcodes::PUTSTATIC => {
2758            pop(&mut stack)?;
2759        }
2760        opcodes::GETFIELD => {
2761            pop(&mut stack)?;
2762            let ty = field_type(insn, cp)?;
2763            stack.push(ty);
2764        }
2765        opcodes::PUTFIELD => {
2766            pop(&mut stack)?;
2767            pop(&mut stack)?;
2768        }
2769        opcodes::INVOKEVIRTUAL..=opcodes::INVOKEDYNAMIC => {
2770            let (args, ret, owner, is_init) = method_type(insn, cp)?;
2771            for _ in 0..args.len() {
2772                pop(&mut stack)?;
2773            }
2774            if insn.opcode != opcodes::INVOKESTATIC && insn.opcode != opcodes::INVOKEDYNAMIC {
2775                let receiver = pop(&mut stack)?;
2776                if is_init {
2777                    let init_owner = if receiver == FrameType::UninitializedThis {
2778                        class_node.name.clone()
2779                    } else {
2780                        owner
2781                    };
2782                    initialize_uninitialized(&mut locals, &mut stack, receiver, init_owner);
2783                }
2784            }
2785            if let Some(ret) = ret {
2786                stack.push(ret);
2787            }
2788        }
2789        opcodes::NEW => {
2790            if let Operand::U2(_index) = insn.operand {
2791                stack.push(FrameType::Uninitialized(insn.offset));
2792            }
2793        }
2794        opcodes::NEWARRAY => {
2795            pop(&mut stack)?;
2796            if let Operand::U1(atype) = insn.operand {
2797                let desc = newarray_descriptor(atype)?;
2798                stack.push(FrameType::Object(desc));
2799            } else {
2800                stack.push(FrameType::Object("[I".to_string()));
2801            }
2802        }
2803        opcodes::ANEWARRAY => {
2804            pop(&mut stack)?;
2805            if let Operand::U2(index) = insn.operand {
2806                let class_name = cp_class_name(cp, index)?;
2807                stack.push(FrameType::Object(format!("[L{class_name};")));
2808            }
2809        }
2810        opcodes::ARRAYLENGTH => {
2811            pop(&mut stack)?;
2812            stack.push(FrameType::Integer);
2813        }
2814        opcodes::ATHROW => {
2815            pop(&mut stack)?;
2816        }
2817        opcodes::CHECKCAST => {
2818            pop(&mut stack)?;
2819            if let Operand::U2(index) = insn.operand {
2820                let class_name = cp_class_name(cp, index)?;
2821                stack.push(FrameType::Object(class_name.to_string()));
2822            }
2823        }
2824        opcodes::INSTANCEOF => {
2825            pop(&mut stack)?;
2826            stack.push(FrameType::Integer);
2827        }
2828        opcodes::MONITORENTER | opcodes::MONITOREXIT => {
2829            pop(&mut stack)?;
2830        }
2831        opcodes::WIDE => {
2832            if let Operand::Wide {
2833                opcode,
2834                index,
2835                increment,
2836            } = insn.operand
2837            {
2838                match opcode {
2839                    opcodes::ILOAD..=opcodes::ALOAD => {
2840                        if let Some(value) = locals.get(index as usize) {
2841                            stack.push(value.clone());
2842                        }
2843                    }
2844                    opcodes::ISTORE..=opcodes::ASTORE => {
2845                        let value = pop(&mut stack)?;
2846                        store_local(&mut locals, index, value);
2847                    }
2848                    opcodes::IINC => {
2849                        let _ = increment;
2850                    }
2851                    opcodes::RET => {}
2852                    _ => {}
2853                }
2854            }
2855        }
2856        opcodes::MULTIANEWARRAY => {
2857            if let Operand::MultiANewArray { dims, .. } = insn.operand {
2858                for _ in 0..dims {
2859                    pop(&mut stack)?;
2860                }
2861                if let Operand::MultiANewArray { index, .. } = insn.operand {
2862                    let desc = cp_class_name(cp, index)?;
2863                    stack.push(FrameType::Object(desc.to_string()));
2864                } else {
2865                    stack.push(FrameType::Object("[Ljava/lang/Object;".to_string()));
2866                }
2867            }
2868        }
2869        opcodes::BREAKPOINT | opcodes::IMPDEP1 | opcodes::IMPDEP2 => {}
2870        _ => {}
2871    }
2872
2873    Ok(FrameState { locals, stack })
2874}
2875
2876fn initialize_uninitialized(
2877    locals: &mut [FrameType],
2878    stack: &mut [FrameType],
2879    receiver: FrameType,
2880    owner: String,
2881) {
2882    let init = FrameType::Object(owner);
2883    for value in locals.iter_mut().chain(stack.iter_mut()) {
2884        if *value == receiver {
2885            *value = init.clone();
2886        }
2887    }
2888}
2889
2890fn is_category2(value: &FrameType) -> bool {
2891    matches!(value, FrameType::Long | FrameType::Double)
2892}
2893
2894fn load_local(locals: &[FrameType], index: u16, fallback: FrameType) -> FrameType {
2895    locals.get(index as usize).cloned().unwrap_or(fallback)
2896}
2897
2898fn store_local(locals: &mut Vec<FrameType>, index: u16, value: FrameType) {
2899    let idx = index as usize;
2900    if locals.len() <= idx {
2901        locals.resize(idx + 1, FrameType::Top);
2902    }
2903    locals[idx] = value.clone();
2904    if is_category2(&value) {
2905        if locals.len() <= idx + 1 {
2906            locals.resize(idx + 2, FrameType::Top);
2907        }
2908        locals[idx + 1] = FrameType::Top;
2909    }
2910}
2911
2912fn array_element_type(value: &FrameType) -> Option<FrameType> {
2913    let FrameType::Object(desc) = value else {
2914        return None;
2915    };
2916    if !desc.starts_with('[') {
2917        return None;
2918    }
2919    let element = &desc[1..];
2920    if element.starts_with('[') {
2921        return Some(FrameType::Object(element.to_string()));
2922    }
2923    let mut chars = element.chars();
2924    match chars.next() {
2925        Some('L') => {
2926            let name = element
2927                .trim_start_matches('L')
2928                .trim_end_matches(';')
2929                .to_string();
2930            Some(FrameType::Object(name))
2931        }
2932        Some('Z') | Some('B') | Some('C') | Some('S') | Some('I') => Some(FrameType::Integer),
2933        Some('F') => Some(FrameType::Float),
2934        Some('J') => Some(FrameType::Long),
2935        Some('D') => Some(FrameType::Double),
2936        _ => None,
2937    }
2938}
2939
2940fn var_index(insn: &ParsedInstruction) -> Result<u16, ClassWriteError> {
2941    match insn.operand {
2942        Operand::U1(value) => Ok(value as u16),
2943        Operand::Wide { index, .. } => Ok(index),
2944        _ => Err(ClassWriteError::FrameComputation(format!(
2945            "missing var index at {}",
2946            insn.offset
2947        ))),
2948    }
2949}
2950
2951fn ldc_type(insn: &ParsedInstruction, cp: &[CpInfo]) -> Result<FrameType, ClassWriteError> {
2952    let index = match insn.operand {
2953        Operand::U1(value) => value as u16,
2954        Operand::U2(value) => value,
2955        _ => {
2956            return Err(ClassWriteError::FrameComputation(format!(
2957                "invalid ldc at {}",
2958                insn.offset
2959            )));
2960        }
2961    };
2962    match cp.get(index as usize) {
2963        Some(CpInfo::Integer(_)) => Ok(FrameType::Integer),
2964        Some(CpInfo::Float(_)) => Ok(FrameType::Float),
2965        Some(CpInfo::Long(_)) => Ok(FrameType::Long),
2966        Some(CpInfo::Double(_)) => Ok(FrameType::Double),
2967        Some(CpInfo::String { .. }) => Ok(FrameType::Object("java/lang/String".to_string())),
2968        Some(CpInfo::Class { .. }) => Ok(FrameType::Object("java/lang/Class".to_string())),
2969        Some(CpInfo::MethodType { .. }) => {
2970            Ok(FrameType::Object("java/lang/invoke/MethodType".to_string()))
2971        }
2972        Some(CpInfo::MethodHandle { .. }) => Ok(FrameType::Object(
2973            "java/lang/invoke/MethodHandle".to_string(),
2974        )),
2975        _ => Ok(FrameType::Top),
2976    }
2977}
2978
2979fn field_type(insn: &ParsedInstruction, cp: &[CpInfo]) -> Result<FrameType, ClassWriteError> {
2980    let index = match insn.operand {
2981        Operand::U2(value) => value,
2982        _ => {
2983            return Err(ClassWriteError::FrameComputation(format!(
2984                "invalid field operand at {}",
2985                insn.offset
2986            )));
2987        }
2988    };
2989    let descriptor = cp_field_descriptor(cp, index)?;
2990    let field_type = parse_field_descriptor(descriptor)?;
2991    Ok(field_type_to_frame(field_type))
2992}
2993
2994fn method_type(
2995    insn: &ParsedInstruction,
2996    cp: &[CpInfo],
2997) -> Result<(Vec<FieldType>, Option<FrameType>, String, bool), ClassWriteError> {
2998    let index = match insn.operand {
2999        Operand::U2(value) => value,
3000        Operand::InvokeInterface { index, .. } => index,
3001        Operand::InvokeDynamic { index } => index,
3002        _ => {
3003            return Err(ClassWriteError::FrameComputation(format!(
3004                "invalid method operand at {}",
3005                insn.offset
3006            )));
3007        }
3008    };
3009    let (owner, descriptor, name) = cp_method_descriptor(cp, index, insn.opcode)?;
3010    let (args, ret) = parse_method_descriptor(descriptor)?;
3011    let ret_frame = match ret {
3012        FieldType::Void => None,
3013        other => Some(field_type_to_frame(other)),
3014    };
3015    Ok((args, ret_frame, owner.to_string(), name == "<init>"))
3016}
3017
3018fn field_type_to_frame(field_type: FieldType) -> FrameType {
3019    match field_type {
3020        FieldType::Boolean
3021        | FieldType::Byte
3022        | FieldType::Char
3023        | FieldType::Short
3024        | FieldType::Int => FrameType::Integer,
3025        FieldType::Float => FrameType::Float,
3026        FieldType::Long => FrameType::Long,
3027        FieldType::Double => FrameType::Double,
3028        FieldType::Object(name) => FrameType::Object(name),
3029        FieldType::Array(desc) => FrameType::Object(desc),
3030        FieldType::Void => FrameType::Top,
3031    }
3032}
3033
3034fn cp_class_name(cp: &[CpInfo], index: u16) -> Result<&str, ClassWriteError> {
3035    match cp.get(index as usize) {
3036        Some(CpInfo::Class { name_index }) => match cp.get(*name_index as usize) {
3037            Some(CpInfo::Utf8(name)) => Ok(name),
3038            _ => Err(ClassWriteError::InvalidConstantPool),
3039        },
3040        _ => Err(ClassWriteError::InvalidConstantPool),
3041    }
3042}
3043
3044fn newarray_descriptor(atype: u8) -> Result<String, ClassWriteError> {
3045    let desc = match atype {
3046        4 => "[Z",
3047        5 => "[C",
3048        6 => "[F",
3049        7 => "[D",
3050        8 => "[B",
3051        9 => "[S",
3052        10 => "[I",
3053        11 => "[J",
3054        _ => {
3055            return Err(ClassWriteError::FrameComputation(
3056                "invalid newarray type".to_string(),
3057            ));
3058        }
3059    };
3060    Ok(desc.to_string())
3061}
3062
3063fn cp_field_descriptor(cp: &[CpInfo], index: u16) -> Result<&str, ClassWriteError> {
3064    match cp.get(index as usize) {
3065        Some(CpInfo::Fieldref {
3066            name_and_type_index,
3067            ..
3068        }) => match cp.get(*name_and_type_index as usize) {
3069            Some(CpInfo::NameAndType {
3070                descriptor_index, ..
3071            }) => match cp.get(*descriptor_index as usize) {
3072                Some(CpInfo::Utf8(desc)) => Ok(desc),
3073                _ => Err(ClassWriteError::InvalidConstantPool),
3074            },
3075            _ => Err(ClassWriteError::InvalidConstantPool),
3076        },
3077        _ => Err(ClassWriteError::InvalidConstantPool),
3078    }
3079}
3080
3081fn cp_method_descriptor(
3082    cp: &[CpInfo],
3083    index: u16,
3084    opcode: u8,
3085) -> Result<(&str, &str, &str), ClassWriteError> {
3086    match cp.get(index as usize) {
3087        Some(CpInfo::Methodref {
3088            class_index,
3089            name_and_type_index,
3090        })
3091        | Some(CpInfo::InterfaceMethodref {
3092            class_index,
3093            name_and_type_index,
3094        }) => {
3095            let owner = cp_class_name(cp, *class_index)?;
3096            match cp.get(*name_and_type_index as usize) {
3097                Some(CpInfo::NameAndType {
3098                    name_index,
3099                    descriptor_index,
3100                }) => {
3101                    let name = cp_utf8(cp, *name_index)?;
3102                    let desc = cp_utf8(cp, *descriptor_index)?;
3103                    Ok((owner, desc, name))
3104                }
3105                _ => Err(ClassWriteError::InvalidConstantPool),
3106            }
3107        }
3108        Some(CpInfo::InvokeDynamic {
3109            name_and_type_index,
3110            ..
3111        }) if opcode == opcodes::INVOKEDYNAMIC => match cp.get(*name_and_type_index as usize) {
3112            Some(CpInfo::NameAndType {
3113                name_index,
3114                descriptor_index,
3115            }) => {
3116                let name = cp_utf8(cp, *name_index)?;
3117                let desc = cp_utf8(cp, *descriptor_index)?;
3118                Ok(("java/lang/Object", desc, name))
3119            }
3120            _ => Err(ClassWriteError::InvalidConstantPool),
3121        },
3122        _ => Err(ClassWriteError::InvalidConstantPool),
3123    }
3124}
3125
3126fn cp_utf8(cp: &[CpInfo], index: u16) -> Result<&str, ClassWriteError> {
3127    match cp.get(index as usize) {
3128        Some(CpInfo::Utf8(value)) => Ok(value.as_str()),
3129        _ => Err(ClassWriteError::InvalidConstantPool),
3130    }
3131}
3132
3133#[derive(Debug, Clone)]
3134enum FieldType {
3135    Boolean,
3136    Byte,
3137    Char,
3138    Short,
3139    Int,
3140    Float,
3141    Long,
3142    Double,
3143    Object(String),
3144    Array(String),
3145    Void,
3146}
3147
3148fn parse_field_descriptor(desc: &str) -> Result<FieldType, ClassWriteError> {
3149    let mut chars = desc.chars().peekable();
3150    parse_field_type(&mut chars)
3151}
3152
3153fn parse_method_descriptor(desc: &str) -> Result<(Vec<FieldType>, FieldType), ClassWriteError> {
3154    let mut chars = desc.chars().peekable();
3155    if chars.next() != Some('(') {
3156        return Err(ClassWriteError::FrameComputation(
3157            "bad method descriptor".to_string(),
3158        ));
3159    }
3160    let mut params = Vec::new();
3161    while let Some(&ch) = chars.peek() {
3162        if ch == ')' {
3163            chars.next();
3164            break;
3165        }
3166        params.push(parse_field_type(&mut chars)?);
3167    }
3168    let ret = parse_return_type(&mut chars)?;
3169    Ok((params, ret))
3170}
3171
3172fn parse_field_type<I>(chars: &mut std::iter::Peekable<I>) -> Result<FieldType, ClassWriteError>
3173where
3174    I: Iterator<Item = char>,
3175{
3176    match chars.next() {
3177        Some('Z') => Ok(FieldType::Boolean),
3178        Some('B') => Ok(FieldType::Byte),
3179        Some('C') => Ok(FieldType::Char),
3180        Some('S') => Ok(FieldType::Short),
3181        Some('I') => Ok(FieldType::Int),
3182        Some('F') => Ok(FieldType::Float),
3183        Some('J') => Ok(FieldType::Long),
3184        Some('D') => Ok(FieldType::Double),
3185        Some('L') => {
3186            let mut name = String::new();
3187            for ch in chars.by_ref() {
3188                if ch == ';' {
3189                    break;
3190                }
3191                name.push(ch);
3192            }
3193            Ok(FieldType::Object(name))
3194        }
3195        Some('[') => {
3196            let mut desc = String::from("[");
3197            let inner = parse_field_type(chars)?;
3198            match inner {
3199                FieldType::Object(name) => {
3200                    desc.push('L');
3201                    desc.push_str(&name);
3202                    desc.push(';');
3203                }
3204                FieldType::Boolean => desc.push('Z'),
3205                FieldType::Byte => desc.push('B'),
3206                FieldType::Char => desc.push('C'),
3207                FieldType::Short => desc.push('S'),
3208                FieldType::Int => desc.push('I'),
3209                FieldType::Float => desc.push('F'),
3210                FieldType::Long => desc.push('J'),
3211                FieldType::Double => desc.push('D'),
3212                FieldType::Void => {}
3213                FieldType::Array(inner_desc) => desc.push_str(&inner_desc),
3214            }
3215            Ok(FieldType::Array(desc))
3216        }
3217        _ => Err(ClassWriteError::FrameComputation(
3218            "bad field descriptor".to_string(),
3219        )),
3220    }
3221}
3222
3223fn parse_return_type<I>(chars: &mut std::iter::Peekable<I>) -> Result<FieldType, ClassWriteError>
3224where
3225    I: Iterator<Item = char>,
3226{
3227    match chars.peek() {
3228        Some('V') => {
3229            chars.next();
3230            Ok(FieldType::Void)
3231        }
3232        _ => parse_field_type(chars),
3233    }
3234}
3235
3236fn read_u1(code: &[u8], pos: &mut usize) -> Result<u8, ClassWriteError> {
3237    if *pos >= code.len() {
3238        return Err(ClassWriteError::FrameComputation(
3239            "unexpected eof".to_string(),
3240        ));
3241    }
3242    let value = code[*pos];
3243    *pos += 1;
3244    Ok(value)
3245}
3246
3247fn read_i1(code: &[u8], pos: &mut usize) -> Result<i8, ClassWriteError> {
3248    Ok(read_u1(code, pos)? as i8)
3249}
3250
3251fn read_u2(code: &[u8], pos: &mut usize) -> Result<u16, ClassWriteError> {
3252    if *pos + 2 > code.len() {
3253        return Err(ClassWriteError::FrameComputation(
3254            "unexpected eof".to_string(),
3255        ));
3256    }
3257    let value = u16::from_be_bytes([code[*pos], code[*pos + 1]]);
3258    *pos += 2;
3259    Ok(value)
3260}
3261
3262fn read_i2(code: &[u8], pos: &mut usize) -> Result<i16, ClassWriteError> {
3263    Ok(read_u2(code, pos)? as i16)
3264}
3265
3266fn read_i4(code: &[u8], pos: &mut usize) -> Result<i32, ClassWriteError> {
3267    if *pos + 4 > code.len() {
3268        return Err(ClassWriteError::FrameComputation(
3269            "unexpected eof".to_string(),
3270        ));
3271    }
3272    let value = i32::from_be_bytes([code[*pos], code[*pos + 1], code[*pos + 2], code[*pos + 3]]);
3273    *pos += 4;
3274    Ok(value)
3275}
3276
3277#[cfg(test)]
3278mod tests {
3279    use super::*;
3280    use crate::opcodes;
3281
3282    #[test]
3283    fn test_constant_pool_deduplication() {
3284        let mut cp = ConstantPoolBuilder::new();
3285        let i1 = cp.utf8("Hello");
3286        let i2 = cp.utf8("World");
3287        let i3 = cp.utf8("Hello");
3288
3289        assert_eq!(i1, 1);
3290        assert_eq!(i2, 2);
3291        assert_eq!(i3, 1, "Duplicate UTF8 should return existing index");
3292
3293        let c1 = cp.class("java/lang/Object");
3294        let c2 = cp.class("java/lang/Object");
3295        assert_eq!(c1, c2, "Duplicate Class should return existing index");
3296    }
3297
3298    #[test]
3299    fn test_basic_class_generation() {
3300        let mut cw = ClassWriter::new(0);
3301        cw.visit(52, 0, 0x0001, "TestClass", Some("java/lang/Object"), &[]);
3302        cw.visit_source_file("TestClass.java");
3303
3304        // Add a field
3305        let mut fv = cw.visit_field(0x0002, "myField", "I");
3306        fv.visit_end(&mut cw);
3307
3308        // Add a default constructor
3309        let mut mv = cw.visit_method(0x0001, "<init>", "()V");
3310        mv.visit_code();
3311        mv.visit_var_insn(opcodes::ALOAD, 0);
3312        mv.visit_method_insn(
3313            opcodes::INVOKESPECIAL,
3314            "java/lang/Object",
3315            "<init>",
3316            "()V",
3317            false,
3318        );
3319        mv.visit_insn(opcodes::RETURN);
3320        mv.visit_maxs(1, 1);
3321        mv.visit_end(&mut cw);
3322
3323        let result = cw.to_bytes();
3324        assert!(result.is_ok(), "Should generate bytes successfully");
3325
3326        let bytes = result.unwrap();
3327        assert!(bytes.len() > 4);
3328        assert_eq!(&bytes[0..4], &[0xCA, 0xFE, 0xBA, 0xBE]); // Magic number
3329    }
3330
3331    #[test]
3332    fn test_compute_frames_flag() {
3333        // Simple linear code, but checking if logic runs without panic
3334        let mut cw = ClassWriter::new(COMPUTE_FRAMES);
3335        cw.visit(52, 0, 0x0001, "FrameTest", Some("java/lang/Object"), &[]);
3336
3337        let mut mv = cw.visit_method(0x0009, "main", "([Ljava/lang/String;)V");
3338        mv.visit_code();
3339        mv.visit_field_insn(
3340            opcodes::GETSTATIC,
3341            "java/lang/System",
3342            "out",
3343            "Ljava/io/PrintStream;",
3344        );
3345        mv.visit_ldc_insn("Hello");
3346        mv.visit_method_insn(
3347            opcodes::INVOKEVIRTUAL,
3348            "java/io/PrintStream",
3349            "println",
3350            "(Ljava/lang/String;)V",
3351            false,
3352        );
3353        mv.visit_insn(opcodes::RETURN);
3354        // maxs should be ignored/recomputed
3355        mv.visit_maxs(0, 0);
3356        mv.visit_end(&mut cw);
3357
3358        let result = cw.to_bytes();
3359        assert!(result.is_ok());
3360    }
3361
3362    #[test]
3363    fn test_class_node_structure() {
3364        let mut cw = ClassWriter::new(0);
3365        cw.visit(52, 0, 0, "MyNode", None, &[]);
3366
3367        let node = cw.to_class_node().expect("Should create class node");
3368        assert_eq!(node.name, "MyNode");
3369        assert_eq!(node.major_version, 52);
3370    }
3371}