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