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