Skip to main content

sqry_classpath/bytecode/
classfile.rs

1//! Class file parser: converts `.class` bytes into [`ClassStub`] records.
2//!
3//! Uses the `cafebabe` crate for low-level JVM bytecode parsing and converts
4//! the parsed representation into our stub model types. This module handles
5//! the base class parsing: class metadata, methods, fields, superclass,
6//! interfaces, inner classes, enum constants, record components, and source
7//! file extraction.
8//!
9//! Generics (U05), annotations (U06), lambdas (U07a), and modules (U07b) are
10//! handled by separate enrichment parsers that post-process the stub.
11
12use cafebabe::attributes::AttributeData;
13use cafebabe::{ClassAccessFlags, FieldAccessFlags, MethodAccessFlags, ParseOptions};
14
15use crate::stub::model::{
16    AccessFlags, ClassKind, ClassStub, FieldStub, InnerClassEntry, MethodStub, RecordComponent,
17};
18use crate::{ClasspathError, ClasspathResult};
19
20use super::constants::{
21    class_name_to_fqn, extract_constant_value, extract_method_parameter_names, extract_source_file,
22    method_descriptor_to_types,
23};
24
25// ---------------------------------------------------------------------------
26// Access flag constants for filtering
27// ---------------------------------------------------------------------------
28
29/// ACC_BRIDGE for methods (0x0040).
30const METHOD_ACC_BRIDGE: u16 = 0x0040;
31/// ACC_SYNTHETIC for methods (0x1000).
32const METHOD_ACC_SYNTHETIC: u16 = 0x1000;
33
34// ---------------------------------------------------------------------------
35// Public API
36// ---------------------------------------------------------------------------
37
38/// Parse a `.class` file's bytes into a [`ClassStub`].
39///
40/// This function handles the base class parsing: class metadata, methods, fields,
41/// superclass, interfaces, inner classes, enum constants, record components, and
42/// source file. It does NOT parse generics (U05), annotations (U06), lambdas (U07a),
43/// or modules (U07b) — those are handled by separate parsers that enrich the stub.
44///
45/// # Errors
46///
47/// Returns [`ClasspathError::BytecodeParseError`] if the bytes cannot be parsed
48/// as a valid class file. Individual method/field parse failures are logged as
49/// warnings and the member is skipped.
50pub fn parse_class(bytes: &[u8]) -> ClasspathResult<ClassStub> {
51    // Parse with bytecode parsing disabled — we only need structure, not opcodes.
52    let mut opts = ParseOptions::default();
53    opts.parse_bytecode(false);
54
55    let class_file = cafebabe::parse_class_with_options(bytes, &opts).map_err(|e| {
56        ClasspathError::BytecodeParseError {
57            class_name: String::from("<unknown>"),
58            reason: e.to_string(),
59        }
60    })?;
61
62    let this_class_raw = class_file.this_class.to_string();
63
64    // Skip module-info and package-info classes — these are handled by U07b.
65    if this_class_raw.ends_with("module-info") || this_class_raw.ends_with("package-info") {
66        return Err(ClasspathError::BytecodeParseError {
67            class_name: class_name_to_fqn(&this_class_raw),
68            reason: "module-info and package-info classes are handled by U07b".to_owned(),
69        });
70    }
71
72    let fqn = class_name_to_fqn(&this_class_raw);
73    let name = extract_simple_name(&fqn);
74    let access = convert_class_access_flags(class_file.access_flags);
75    let kind = determine_class_kind(class_file.access_flags, &class_file.attributes);
76
77    let superclass = class_file
78        .super_class
79        .as_ref()
80        .map(|sc| class_name_to_fqn(sc));
81
82    let interfaces: Vec<String> = class_file
83        .interfaces
84        .iter()
85        .map(|i| class_name_to_fqn(i))
86        .collect();
87
88    // Parse methods (skipping synthetic and bridge methods).
89    let methods = parse_methods(&class_file.methods, &fqn);
90
91    // Parse fields.
92    let fields = parse_fields(&class_file.fields, &fqn);
93
94    // Extract enum constants (static final fields whose type matches the class).
95    let enum_constants = if kind == ClassKind::Enum {
96        extract_enum_constants(&class_file.fields, &this_class_raw)
97    } else {
98        vec![]
99    };
100
101    // Extract inner classes from the InnerClasses attribute.
102    let inner_classes = extract_inner_classes(&class_file.attributes);
103
104    // Extract record components from the Record attribute.
105    let record_components = extract_record_components(&class_file.attributes);
106
107    // Extract source file name.
108    let source_file = extract_source_file(&class_file.attributes);
109
110    Ok(ClassStub {
111        fqn,
112        name,
113        kind,
114        access,
115        superclass,
116        interfaces,
117        methods,
118        fields,
119        annotations: vec![],     // Populated by U06 enrichment parser.
120        generic_signature: None, // Populated by U05 enrichment parser.
121        inner_classes,
122        lambda_targets: vec![], // Populated by U07a enrichment parser.
123        module: None,           // Populated by U07b enrichment parser.
124        record_components,
125        enum_constants,
126        source_file,
127        source_jar: None,      // Set by scan_jar() after parsing.
128        kotlin_metadata: None, // Populated by Kotlin metadata decoder.
129        scala_signature: None, // Populated by Scala signature decoder.
130    })
131}
132
133// ---------------------------------------------------------------------------
134// Class metadata helpers
135// ---------------------------------------------------------------------------
136
137/// Extract the simple name from a fully qualified name.
138///
139/// For `"java.util.HashMap"` returns `"HashMap"`.
140/// For `"java.util.Map.Entry"` returns `"Entry"`.
141fn extract_simple_name(fqn: &str) -> String {
142    // Inner classes use `$` separator in bytecode but `.` in our FQN.
143    // The simple name is the last segment after the last `.`.
144    fqn.rsplit('.').next().unwrap_or(fqn).to_owned()
145}
146
147/// Convert `cafebabe` class access flags to our [`AccessFlags`].
148fn convert_class_access_flags(flags: ClassAccessFlags) -> AccessFlags {
149    AccessFlags::new(flags.bits())
150}
151
152/// Convert `cafebabe` method access flags to our [`AccessFlags`].
153fn convert_method_access_flags(flags: MethodAccessFlags) -> AccessFlags {
154    AccessFlags::new(flags.bits())
155}
156
157/// Convert `cafebabe` field access flags to our [`AccessFlags`].
158fn convert_field_access_flags(flags: FieldAccessFlags) -> AccessFlags {
159    AccessFlags::new(flags.bits())
160}
161
162/// Determine the [`ClassKind`] from access flags and attributes.
163///
164/// Order of precedence (per JVMS):
165/// 1. `ACC_MODULE` → `Module`
166/// 2. `ACC_ANNOTATION` + `ACC_INTERFACE` → `Annotation`
167/// 3. `ACC_ENUM` → `Enum`
168/// 4. `ACC_INTERFACE` → `Interface`
169/// 5. Has `Record` attribute → `Record`
170/// 6. Otherwise → `Class`
171fn determine_class_kind(
172    flags: ClassAccessFlags,
173    attrs: &[cafebabe::attributes::AttributeInfo<'_>],
174) -> ClassKind {
175    if flags.contains(ClassAccessFlags::MODULE) {
176        return ClassKind::Module;
177    }
178    if flags.contains(ClassAccessFlags::ANNOTATION) && flags.contains(ClassAccessFlags::INTERFACE) {
179        return ClassKind::Annotation;
180    }
181    if flags.contains(ClassAccessFlags::ENUM) {
182        return ClassKind::Enum;
183    }
184    if flags.contains(ClassAccessFlags::INTERFACE) {
185        return ClassKind::Interface;
186    }
187    // Check for Record attribute (Java 16+).
188    let has_record = attrs
189        .iter()
190        .any(|a| matches!(&a.data, AttributeData::Record(_)));
191    if has_record {
192        return ClassKind::Record;
193    }
194    ClassKind::Class
195}
196
197// ---------------------------------------------------------------------------
198// Method parsing
199// ---------------------------------------------------------------------------
200
201/// Parse all methods from a class file, filtering out synthetic and bridge methods.
202fn parse_methods(methods: &[cafebabe::MethodInfo<'_>], class_fqn: &str) -> Vec<MethodStub> {
203    let mut result = Vec::with_capacity(methods.len());
204    for method in methods {
205        let raw_bits = method.access_flags.bits();
206
207        // Skip synthetic and bridge methods.
208        if raw_bits & METHOD_ACC_BRIDGE != 0 || raw_bits & METHOD_ACC_SYNTHETIC != 0 {
209            continue;
210        }
211
212        match parse_single_method(method) {
213            Ok(stub) => result.push(stub),
214            Err(e) => {
215                log::warn!(
216                    "Skipping method '{}' in class '{}': {}",
217                    method.name,
218                    class_fqn,
219                    e
220                );
221            }
222        }
223    }
224    result
225}
226
227/// Parse a single method into a [`MethodStub`].
228fn parse_single_method(method: &cafebabe::MethodInfo<'_>) -> ClasspathResult<MethodStub> {
229    let name = method.name.to_string();
230    let access = convert_method_access_flags(method.access_flags);
231    let descriptor = method.descriptor.to_string();
232
233    let (parameter_types, return_type) = method_descriptor_to_types(&method.descriptor);
234
235    // Extract parameter names from MethodParameters attribute.
236    let parameter_names = extract_method_parameter_names(&method.attributes);
237
238    Ok(MethodStub {
239        name,
240        access,
241        descriptor,
242        generic_signature: None,       // Populated by U05 enrichment parser.
243        annotations: vec![],           // Populated by U06 enrichment parser.
244        parameter_annotations: vec![], // Populated by U06 enrichment parser.
245        parameter_names,
246        return_type,
247        parameter_types,
248    })
249}
250
251// ---------------------------------------------------------------------------
252// Field parsing
253// ---------------------------------------------------------------------------
254
255/// Parse all fields from a class file.
256fn parse_fields(fields: &[cafebabe::FieldInfo<'_>], class_fqn: &str) -> Vec<FieldStub> {
257    let mut result = Vec::with_capacity(fields.len());
258    for field in fields {
259        match parse_single_field(field) {
260            Ok(stub) => result.push(stub),
261            Err(e) => {
262                log::warn!(
263                    "Skipping field '{}' in class '{}': {}",
264                    field.name,
265                    class_fqn,
266                    e
267                );
268            }
269        }
270    }
271    result
272}
273
274/// Parse a single field into a [`FieldStub`].
275fn parse_single_field(field: &cafebabe::FieldInfo<'_>) -> ClasspathResult<FieldStub> {
276    let name = field.name.to_string();
277    let access = convert_field_access_flags(field.access_flags);
278    let descriptor = field.descriptor.to_string();
279
280    // Extract constant value for static final fields.
281    let constant_value = if access.is_static() && access.is_final() {
282        extract_constant_value(&field.attributes)
283    } else {
284        None
285    };
286
287    Ok(FieldStub {
288        name,
289        access,
290        descriptor,
291        generic_signature: None, // Populated by U05 enrichment parser.
292        annotations: vec![],     // Populated by U06 enrichment parser.
293        constant_value,
294    })
295}
296
297// ---------------------------------------------------------------------------
298// Enum constant extraction
299// ---------------------------------------------------------------------------
300
301/// Extract enum constant names from fields.
302///
303/// Enum constants are `static final` fields whose type descriptor matches the
304/// containing class (and have `ACC_ENUM` set).
305fn extract_enum_constants(
306    fields: &[cafebabe::FieldInfo<'_>],
307    this_class_internal: &str,
308) -> Vec<String> {
309    let expected_descriptor = format!("L{this_class_internal};");
310    fields
311        .iter()
312        .filter(|f| {
313            let bits = f.access_flags.bits();
314            // Must be static, final, and marked as enum constant.
315            bits & FieldAccessFlags::STATIC.bits() != 0
316                && bits & FieldAccessFlags::FINAL.bits() != 0
317                && bits & FieldAccessFlags::ENUM.bits() != 0
318                && f.descriptor.to_string() == expected_descriptor
319        })
320        .map(|f| f.name.to_string())
321        .collect()
322}
323
324// ---------------------------------------------------------------------------
325// Inner class extraction
326// ---------------------------------------------------------------------------
327
328/// Extract inner class entries from the `InnerClasses` attribute.
329fn extract_inner_classes(
330    attrs: &[cafebabe::attributes::AttributeInfo<'_>],
331) -> Vec<InnerClassEntry> {
332    let mut result = Vec::new();
333    for attr in attrs {
334        if let AttributeData::InnerClasses(entries) = &attr.data {
335            for entry in entries {
336                result.push(InnerClassEntry {
337                    inner_fqn: class_name_to_fqn(&entry.inner_class_info),
338                    outer_fqn: entry
339                        .outer_class_info
340                        .as_ref()
341                        .map(|o| class_name_to_fqn(o)),
342                    inner_name: entry.inner_name.as_ref().map(|n| n.to_string()),
343                    access: AccessFlags::new(entry.access_flags.bits()),
344                });
345            }
346        }
347    }
348    result
349}
350
351// ---------------------------------------------------------------------------
352// Record component extraction
353// ---------------------------------------------------------------------------
354
355/// Extract record components from the `Record` attribute (Java 16+).
356fn extract_record_components(
357    attrs: &[cafebabe::attributes::AttributeInfo<'_>],
358) -> Vec<RecordComponent> {
359    let mut result = Vec::new();
360    for attr in attrs {
361        if let AttributeData::Record(components) = &attr.data {
362            for comp in components {
363                result.push(RecordComponent {
364                    name: comp.name.to_string(),
365                    descriptor: comp.descriptor.to_string(),
366                    generic_signature: None, // Populated by U05 enrichment parser.
367                    annotations: vec![],     // Populated by U06 enrichment parser.
368                });
369            }
370        }
371    }
372    result
373}
374
375// ---------------------------------------------------------------------------
376// Tests
377// ---------------------------------------------------------------------------
378
379#[cfg(test)]
380mod tests {
381    use super::*;
382    use crate::stub::model::{BaseType, ConstantValue, TypeSignature};
383
384    // -----------------------------------------------------------------------
385    // Minimal class file builder for tests
386    // -----------------------------------------------------------------------
387
388    /// Builder for constructing minimal valid Java class file bytes.
389    ///
390    /// Produces valid class files that `cafebabe` can parse, with configurable
391    /// class name, access flags, superclass, interfaces, fields, methods, and
392    /// attributes.
393    struct ClassFileBuilder {
394        /// Major version (52 = Java 8).
395        major_version: u16,
396        /// Constant pool entries (raw bytes, each entry prefixed with tag byte).
397        cp_entries: Vec<Vec<u8>>,
398        /// Access flags for the class.
399        access_flags: u16,
400        /// Constant pool index of this class.
401        this_class_idx: u16,
402        /// Constant pool index of super class (0 for java/lang/Object).
403        super_class_idx: u16,
404        /// Interface constant pool indices.
405        interface_indices: Vec<u16>,
406        /// Raw field bytes.
407        fields: Vec<Vec<u8>>,
408        /// Raw method bytes.
409        methods: Vec<Vec<u8>>,
410        /// Raw attribute bytes.
411        attributes: Vec<Vec<u8>>,
412    }
413
414    impl ClassFileBuilder {
415        /// Create a builder with a given class name and default superclass
416        /// (`java/lang/Object`).
417        fn new(class_name: &str) -> Self {
418            let mut builder = Self {
419                major_version: 52,
420                cp_entries: Vec::new(),
421                access_flags: 0x0021, // ACC_PUBLIC | ACC_SUPER
422                this_class_idx: 0,
423                super_class_idx: 0,
424                interface_indices: Vec::new(),
425                fields: Vec::new(),
426                methods: Vec::new(),
427                attributes: Vec::new(),
428            };
429            // Add class name and java/lang/Object to constant pool.
430            let class_name_idx = builder.add_utf8(class_name);
431            builder.this_class_idx = builder.add_class(class_name_idx);
432            let object_name_idx = builder.add_utf8("java/lang/Object");
433            builder.super_class_idx = builder.add_class(object_name_idx);
434            builder
435        }
436
437        /// Add a UTF-8 constant pool entry. Returns 1-based index.
438        fn add_utf8(&mut self, s: &str) -> u16 {
439            let mut entry = vec![1u8]; // CONSTANT_Utf8
440            let bytes = s.as_bytes();
441            entry.extend_from_slice(&(bytes.len() as u16).to_be_bytes());
442            entry.extend_from_slice(bytes);
443            self.cp_entries.push(entry);
444            self.cp_entries.len() as u16
445        }
446
447        /// Add a Class constant pool entry. Returns 1-based index.
448        fn add_class(&mut self, name_idx: u16) -> u16 {
449            let mut entry = vec![7u8]; // CONSTANT_Class
450            entry.extend_from_slice(&name_idx.to_be_bytes());
451            self.cp_entries.push(entry);
452            self.cp_entries.len() as u16
453        }
454
455        /// Add an Integer constant pool entry. Returns 1-based index.
456        fn add_integer(&mut self, value: i32) -> u16 {
457            let mut entry = vec![3u8]; // CONSTANT_Integer
458            entry.extend_from_slice(&value.to_be_bytes());
459            self.cp_entries.push(entry);
460            self.cp_entries.len() as u16
461        }
462
463        /// Add a String constant pool entry. Returns 1-based index.
464        fn add_string(&mut self, utf8_idx: u16) -> u16 {
465            let mut entry = vec![8u8]; // CONSTANT_String
466            entry.extend_from_slice(&utf8_idx.to_be_bytes());
467            self.cp_entries.push(entry);
468            self.cp_entries.len() as u16
469        }
470
471        /// Set access flags.
472        fn access_flags(mut self, flags: u16) -> Self {
473            self.access_flags = flags;
474            self
475        }
476
477        /// Set superclass to none (for java.lang.Object itself).
478        fn no_superclass(mut self) -> Self {
479            self.super_class_idx = 0;
480            self
481        }
482
483        /// Add a superclass by name. Replaces the default `java/lang/Object`.
484        fn superclass(mut self, name: &str) -> Self {
485            let name_idx = self.add_utf8(name);
486            self.super_class_idx = self.add_class(name_idx);
487            self
488        }
489
490        /// Add an interface.
491        fn add_interface(&mut self, name: &str) -> &mut Self {
492            let name_idx = self.add_utf8(name);
493            let class_idx = self.add_class(name_idx);
494            self.interface_indices.push(class_idx);
495            self
496        }
497
498        /// Add a field with access flags and descriptor.
499        fn add_field(
500            &mut self,
501            name: &str,
502            descriptor: &str,
503            access_flags: u16,
504            constant_value_cp_idx: Option<u16>,
505        ) -> &mut Self {
506            let name_idx = self.add_utf8(name);
507            let desc_idx = self.add_utf8(descriptor);
508
509            let mut field_bytes = Vec::new();
510            field_bytes.extend_from_slice(&access_flags.to_be_bytes());
511            field_bytes.extend_from_slice(&name_idx.to_be_bytes());
512            field_bytes.extend_from_slice(&desc_idx.to_be_bytes());
513
514            if let Some(cv_idx) = constant_value_cp_idx {
515                // 1 attribute: ConstantValue
516                let attr_name_idx = self.add_utf8("ConstantValue");
517                field_bytes.extend_from_slice(&1u16.to_be_bytes()); // attributes_count
518                field_bytes.extend_from_slice(&attr_name_idx.to_be_bytes());
519                field_bytes.extend_from_slice(&2u32.to_be_bytes()); // attribute_length
520                field_bytes.extend_from_slice(&cv_idx.to_be_bytes());
521            } else {
522                field_bytes.extend_from_slice(&0u16.to_be_bytes()); // attributes_count = 0
523            }
524
525            self.fields.push(field_bytes);
526            self
527        }
528
529        /// Add a method with access flags and descriptor.
530        fn add_method(&mut self, name: &str, descriptor: &str, access_flags: u16) -> &mut Self {
531            let name_idx = self.add_utf8(name);
532            let desc_idx = self.add_utf8(descriptor);
533
534            let mut method_bytes = Vec::new();
535            method_bytes.extend_from_slice(&access_flags.to_be_bytes());
536            method_bytes.extend_from_slice(&name_idx.to_be_bytes());
537            method_bytes.extend_from_slice(&desc_idx.to_be_bytes());
538            method_bytes.extend_from_slice(&0u16.to_be_bytes()); // attributes_count = 0
539
540            self.methods.push(method_bytes);
541            self
542        }
543
544        /// Add a method with MethodParameters attribute.
545        fn add_method_with_params(
546            &mut self,
547            name: &str,
548            descriptor: &str,
549            access_flags: u16,
550            param_names: &[&str],
551        ) -> &mut Self {
552            let name_idx = self.add_utf8(name);
553            let desc_idx = self.add_utf8(descriptor);
554
555            let mut method_bytes = Vec::new();
556            method_bytes.extend_from_slice(&access_flags.to_be_bytes());
557            method_bytes.extend_from_slice(&name_idx.to_be_bytes());
558            method_bytes.extend_from_slice(&desc_idx.to_be_bytes());
559
560            // Build MethodParameters attribute.
561            let attr_name_idx = self.add_utf8("MethodParameters");
562            let param_name_indices: Vec<u16> =
563                param_names.iter().map(|pn| self.add_utf8(pn)).collect();
564
565            // 1 attribute
566            method_bytes.extend_from_slice(&1u16.to_be_bytes());
567            method_bytes.extend_from_slice(&attr_name_idx.to_be_bytes());
568            // attribute_length: 1 byte (parameters_count) + 4 bytes per param
569            let attr_length = 1 + param_name_indices.len() as u32 * 4;
570            method_bytes.extend_from_slice(&attr_length.to_be_bytes());
571            method_bytes.push(param_name_indices.len() as u8);
572            for idx in &param_name_indices {
573                method_bytes.extend_from_slice(&idx.to_be_bytes());
574                method_bytes.extend_from_slice(&0u16.to_be_bytes()); // access_flags
575            }
576
577            self.methods.push(method_bytes);
578            self
579        }
580
581        /// Add an InnerClasses attribute.
582        fn add_inner_classes_attribute(
583            &mut self,
584            entries: &[(&str, Option<&str>, Option<&str>, u16)],
585        ) -> &mut Self {
586            let attr_name_idx = self.add_utf8("InnerClasses");
587
588            let mut attr_data = Vec::new();
589            attr_data.extend_from_slice(&(entries.len() as u16).to_be_bytes());
590
591            for (inner, outer, inner_name, flags) in entries {
592                let inner_name_idx = self.add_utf8(inner);
593                let inner_class_idx = self.add_class(inner_name_idx);
594                attr_data.extend_from_slice(&inner_class_idx.to_be_bytes());
595
596                if let Some(outer_name) = outer {
597                    let outer_name_idx = self.add_utf8(outer_name);
598                    let outer_class_idx = self.add_class(outer_name_idx);
599                    attr_data.extend_from_slice(&outer_class_idx.to_be_bytes());
600                } else {
601                    attr_data.extend_from_slice(&0u16.to_be_bytes());
602                }
603
604                if let Some(name) = inner_name {
605                    let name_idx = self.add_utf8(name);
606                    attr_data.extend_from_slice(&name_idx.to_be_bytes());
607                } else {
608                    attr_data.extend_from_slice(&0u16.to_be_bytes());
609                }
610
611                attr_data.extend_from_slice(&flags.to_be_bytes());
612            }
613
614            let mut attr_bytes = Vec::new();
615            attr_bytes.extend_from_slice(&attr_name_idx.to_be_bytes());
616            attr_bytes.extend_from_slice(&(attr_data.len() as u32).to_be_bytes());
617            attr_bytes.extend_from_slice(&attr_data);
618
619            self.attributes.push(attr_bytes);
620            self
621        }
622
623        /// Add a SourceFile attribute.
624        fn add_source_file_attribute(&mut self, source_file: &str) -> &mut Self {
625            let attr_name_idx = self.add_utf8("SourceFile");
626            let source_idx = self.add_utf8(source_file);
627
628            let mut attr_bytes = Vec::new();
629            attr_bytes.extend_from_slice(&attr_name_idx.to_be_bytes());
630            attr_bytes.extend_from_slice(&2u32.to_be_bytes()); // attribute_length
631            attr_bytes.extend_from_slice(&source_idx.to_be_bytes());
632
633            self.attributes.push(attr_bytes);
634            self
635        }
636
637        /// Build the class file bytes.
638        fn build(&self) -> Vec<u8> {
639            let mut bytes = Vec::new();
640
641            // Magic
642            bytes.extend_from_slice(&0xCAFE_BABEu32.to_be_bytes());
643            // Minor version
644            bytes.extend_from_slice(&0u16.to_be_bytes());
645            // Major version
646            bytes.extend_from_slice(&self.major_version.to_be_bytes());
647
648            // Constant pool count (entries + 1)
649            let cp_count = self.cp_entries.len() as u16 + 1;
650            bytes.extend_from_slice(&cp_count.to_be_bytes());
651            for entry in &self.cp_entries {
652                bytes.extend_from_slice(entry);
653            }
654
655            // Access flags
656            bytes.extend_from_slice(&self.access_flags.to_be_bytes());
657            // This class
658            bytes.extend_from_slice(&self.this_class_idx.to_be_bytes());
659            // Super class
660            bytes.extend_from_slice(&self.super_class_idx.to_be_bytes());
661
662            // Interfaces
663            bytes.extend_from_slice(&(self.interface_indices.len() as u16).to_be_bytes());
664            for idx in &self.interface_indices {
665                bytes.extend_from_slice(&idx.to_be_bytes());
666            }
667
668            // Fields
669            bytes.extend_from_slice(&(self.fields.len() as u16).to_be_bytes());
670            for field in &self.fields {
671                bytes.extend_from_slice(field);
672            }
673
674            // Methods
675            bytes.extend_from_slice(&(self.methods.len() as u16).to_be_bytes());
676            for method in &self.methods {
677                bytes.extend_from_slice(method);
678            }
679
680            // Attributes
681            bytes.extend_from_slice(&(self.attributes.len() as u16).to_be_bytes());
682            for attr in &self.attributes {
683                bytes.extend_from_slice(attr);
684            }
685
686            bytes
687        }
688    }
689
690    // -----------------------------------------------------------------------
691    // Tests
692    // -----------------------------------------------------------------------
693
694    #[test]
695    fn test_parse_minimal_class() {
696        let bytes = ClassFileBuilder::new("com/example/Minimal").build();
697        let stub = parse_class(&bytes).unwrap();
698
699        assert_eq!(stub.fqn, "com.example.Minimal");
700        assert_eq!(stub.name, "Minimal");
701        assert_eq!(stub.kind, ClassKind::Class);
702        assert!(stub.access.is_public());
703        assert_eq!(stub.superclass.as_deref(), Some("java.lang.Object"));
704        assert!(stub.interfaces.is_empty());
705        assert!(stub.methods.is_empty());
706        assert!(stub.fields.is_empty());
707        assert!(stub.inner_classes.is_empty());
708        assert!(stub.enum_constants.is_empty());
709        assert!(stub.record_components.is_empty());
710    }
711
712    #[test]
713    fn test_parse_class_with_methods_and_fields() {
714        let mut builder = ClassFileBuilder::new("com/example/MyClass");
715        builder.add_method("toString", "()Ljava/lang/String;", 0x0001); // public
716        builder.add_method("hashCode", "()I", 0x0001); // public
717        builder.add_field("name", "Ljava/lang/String;", 0x0002, None); // private
718        builder.add_field("age", "I", 0x0001, None); // public
719
720        let bytes = builder.build();
721        let stub = parse_class(&bytes).unwrap();
722
723        assert_eq!(stub.methods.len(), 2);
724        assert_eq!(stub.methods[0].name, "toString");
725        assert_eq!(stub.methods[0].descriptor, "()Ljava/lang/String;");
726        assert!(stub.methods[0].access.is_public());
727        assert_eq!(stub.methods[1].name, "hashCode");
728
729        assert_eq!(stub.fields.len(), 2);
730        assert_eq!(stub.fields[0].name, "name");
731        assert!(stub.fields[0].access.is_private());
732        assert_eq!(stub.fields[1].name, "age");
733        assert_eq!(stub.fields[1].descriptor, "I");
734    }
735
736    #[test]
737    fn test_parse_enum_class() {
738        let mut builder = ClassFileBuilder::new("com/example/Color");
739        // ACC_PUBLIC | ACC_FINAL | ACC_SUPER | ACC_ENUM
740        builder = builder.access_flags(0x0001 | 0x0010 | 0x0020 | 0x4000);
741        // Superclass is java/lang/Enum
742        builder = builder.superclass("java/lang/Enum");
743
744        // Enum constants: static final fields of the enum type with ACC_ENUM
745        // ACC_PUBLIC | ACC_STATIC | ACC_FINAL | ACC_ENUM = 0x4019
746        builder.add_field("RED", "Lcom/example/Color;", 0x4019, None);
747        builder.add_field("GREEN", "Lcom/example/Color;", 0x4019, None);
748        builder.add_field("BLUE", "Lcom/example/Color;", 0x4019, None);
749
750        // Non-enum field
751        builder.add_field("rgb", "I", 0x0012, None); // private final
752
753        let bytes = builder.build();
754        let stub = parse_class(&bytes).unwrap();
755
756        assert_eq!(stub.kind, ClassKind::Enum);
757        assert_eq!(stub.enum_constants, vec!["RED", "GREEN", "BLUE"]);
758        assert_eq!(stub.superclass.as_deref(), Some("java.lang.Enum"));
759    }
760
761    #[test]
762    fn test_parse_interface() {
763        let builder = ClassFileBuilder::new("com/example/Readable").access_flags(0x0601); // ACC_PUBLIC | ACC_INTERFACE | ACC_ABSTRACT
764        let bytes = builder.build();
765        let stub = parse_class(&bytes).unwrap();
766
767        assert_eq!(stub.kind, ClassKind::Interface);
768        assert!(stub.access.is_interface());
769        assert!(stub.access.is_abstract());
770    }
771
772    #[test]
773    fn test_parse_class_with_inner_classes() {
774        let mut builder = ClassFileBuilder::new("com/example/Outer");
775        builder.add_inner_classes_attribute(&[
776            (
777                "com/example/Outer$Inner",
778                Some("com/example/Outer"),
779                Some("Inner"),
780                0x0001, // public
781            ),
782            (
783                "com/example/Outer$1",
784                None,   // anonymous: no outer
785                None,   // anonymous: no inner name
786                0x0000, // package-private
787            ),
788        ]);
789
790        let bytes = builder.build();
791        let stub = parse_class(&bytes).unwrap();
792
793        assert_eq!(stub.inner_classes.len(), 2);
794
795        assert_eq!(stub.inner_classes[0].inner_fqn, "com.example.Outer$Inner");
796        assert_eq!(
797            stub.inner_classes[0].outer_fqn.as_deref(),
798            Some("com.example.Outer")
799        );
800        assert_eq!(stub.inner_classes[0].inner_name.as_deref(), Some("Inner"));
801        assert!(stub.inner_classes[0].access.is_public());
802
803        assert_eq!(stub.inner_classes[1].inner_fqn, "com.example.Outer$1");
804        assert!(stub.inner_classes[1].outer_fqn.is_none());
805        assert!(stub.inner_classes[1].inner_name.is_none());
806    }
807
808    #[test]
809    fn test_parse_class_with_constant_fields() {
810        let mut builder = ClassFileBuilder::new("com/example/Constants");
811
812        // Static final int
813        let int_idx = builder.add_integer(42);
814        builder.add_field("MAX_SIZE", "I", 0x0019, Some(int_idx)); // public static final
815
816        // Static final String
817        let str_utf8_idx = builder.add_utf8("hello");
818        let str_idx = builder.add_string(str_utf8_idx);
819        builder.add_field("DEFAULT_NAME", "Ljava/lang/String;", 0x0019, Some(str_idx));
820
821        // Non-static-final field should not have constant value extracted
822        builder.add_field("mutable", "I", 0x0001, None);
823
824        let bytes = builder.build();
825        let stub = parse_class(&bytes).unwrap();
826
827        assert_eq!(stub.fields.len(), 3);
828
829        // MAX_SIZE = 42
830        assert_eq!(stub.fields[0].name, "MAX_SIZE");
831        assert_eq!(stub.fields[0].constant_value, Some(ConstantValue::Int(42)));
832
833        // DEFAULT_NAME = "hello"
834        assert_eq!(stub.fields[1].name, "DEFAULT_NAME");
835        assert_eq!(
836            stub.fields[1].constant_value,
837            Some(ConstantValue::String("hello".to_owned()))
838        );
839
840        // mutable has no constant value
841        assert!(stub.fields[2].constant_value.is_none());
842    }
843
844    #[test]
845    fn test_synthetic_methods_filtered() {
846        let mut builder = ClassFileBuilder::new("com/example/Filtered");
847        builder.add_method("realMethod", "()V", 0x0001); // public
848        builder.add_method("access$000", "()V", METHOD_ACC_SYNTHETIC); // synthetic
849        builder.add_method("bridge$0", "()V", METHOD_ACC_BRIDGE); // bridge
850
851        let bytes = builder.build();
852        let stub = parse_class(&bytes).unwrap();
853
854        assert_eq!(stub.methods.len(), 1);
855        assert_eq!(stub.methods[0].name, "realMethod");
856    }
857
858    #[test]
859    fn test_bridge_and_synthetic_combined_filtered() {
860        let mut builder = ClassFileBuilder::new("com/example/BridgeSynthetic");
861        builder.add_method("realMethod", "()V", 0x0001);
862        // Both bridge and synthetic set
863        builder.add_method("combined", "()V", METHOD_ACC_BRIDGE | METHOD_ACC_SYNTHETIC);
864
865        let bytes = builder.build();
866        let stub = parse_class(&bytes).unwrap();
867
868        assert_eq!(stub.methods.len(), 1);
869        assert_eq!(stub.methods[0].name, "realMethod");
870    }
871
872    #[test]
873    fn test_malformed_bytes_returns_error() {
874        // Empty bytes
875        assert!(parse_class(&[]).is_err());
876
877        // Wrong magic
878        assert!(parse_class(&[0xDE, 0xAD, 0xBE, 0xEF]).is_err());
879
880        // Truncated class file (just magic + version)
881        assert!(parse_class(&[0xCA, 0xFE, 0xBA, 0xBE, 0x00, 0x00, 0x00, 0x34]).is_err());
882
883        // Random garbage
884        assert!(parse_class(&[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]).is_err());
885    }
886
887    #[test]
888    fn test_method_descriptor_parsing_produces_correct_types() {
889        let mut builder = ClassFileBuilder::new("com/example/Types");
890        // Method: void process(int, String, double[])
891        builder.add_method("process", "(ILjava/lang/String;[D)V", 0x0001);
892
893        let bytes = builder.build();
894        let stub = parse_class(&bytes).unwrap();
895
896        assert_eq!(stub.methods.len(), 1);
897        let method = &stub.methods[0];
898        assert_eq!(method.parameter_types.len(), 3);
899
900        assert!(matches!(
901            method.parameter_types[0],
902            TypeSignature::Base(BaseType::Int)
903        ));
904        match &method.parameter_types[1] {
905            TypeSignature::Class { fqn, .. } => assert_eq!(fqn, "java.lang.String"),
906            other => panic!("Expected Class, got {other:?}"),
907        }
908        match &method.parameter_types[2] {
909            TypeSignature::Array(inner) => {
910                assert!(matches!(
911                    inner.as_ref(),
912                    TypeSignature::Base(BaseType::Double)
913                ));
914            }
915            other => panic!("Expected Array, got {other:?}"),
916        }
917        assert!(matches!(
918            method.return_type,
919            TypeSignature::Base(BaseType::Void)
920        ));
921    }
922
923    #[test]
924    fn test_access_flags_combinations() {
925        // public abstract
926        let builder = ClassFileBuilder::new("com/example/Abstract").access_flags(0x0421); // PUBLIC | SUPER | ABSTRACT
927        let bytes = builder.build();
928        let stub = parse_class(&bytes).unwrap();
929        assert!(stub.access.is_public());
930        assert!(stub.access.is_abstract());
931
932        // public final
933        let builder = ClassFileBuilder::new("com/example/Final").access_flags(0x0031); // PUBLIC | SUPER | FINAL
934        let bytes = builder.build();
935        let stub = parse_class(&bytes).unwrap();
936        assert!(stub.access.is_public());
937        assert!(stub.access.is_final());
938    }
939
940    #[test]
941    fn test_class_with_interfaces() {
942        let mut builder = ClassFileBuilder::new("com/example/MyList");
943        builder.add_interface("java/io/Serializable");
944        builder.add_interface("java/lang/Comparable");
945
946        let bytes = builder.build();
947        let stub = parse_class(&bytes).unwrap();
948
949        assert_eq!(stub.interfaces.len(), 2);
950        assert_eq!(stub.interfaces[0], "java.io.Serializable");
951        assert_eq!(stub.interfaces[1], "java.lang.Comparable");
952    }
953
954    #[test]
955    fn test_source_file_attribute() {
956        let mut builder = ClassFileBuilder::new("com/example/WithSource");
957        builder.add_source_file_attribute("WithSource.java");
958
959        let bytes = builder.build();
960        let stub = parse_class(&bytes).unwrap();
961
962        assert_eq!(stub.source_file.as_deref(), Some("WithSource.java"));
963    }
964
965    #[test]
966    fn test_method_with_parameter_names() {
967        let mut builder = ClassFileBuilder::new("com/example/Params");
968        builder.add_method_with_params(
969            "greet",
970            "(Ljava/lang/String;I)V",
971            0x0001, // public
972            &["name", "count"],
973        );
974
975        let bytes = builder.build();
976        let stub = parse_class(&bytes).unwrap();
977
978        assert_eq!(stub.methods.len(), 1);
979        assert_eq!(stub.methods[0].parameter_names, vec!["name", "count"]);
980    }
981
982    #[test]
983    fn test_annotation_type() {
984        // ACC_PUBLIC | ACC_INTERFACE | ACC_ABSTRACT | ACC_ANNOTATION
985        let builder = ClassFileBuilder::new("com/example/MyAnnotation").access_flags(0x2601);
986        let bytes = builder.build();
987        let stub = parse_class(&bytes).unwrap();
988        assert_eq!(stub.kind, ClassKind::Annotation);
989    }
990
991    #[test]
992    fn test_module_info_skipped() {
993        let builder = ClassFileBuilder::new("module-info");
994        let bytes = builder.build();
995        let result = parse_class(&bytes);
996        assert!(result.is_err());
997    }
998
999    #[test]
1000    fn test_package_info_skipped() {
1001        let builder = ClassFileBuilder::new("com/example/package-info");
1002        let bytes = builder.build();
1003        let result = parse_class(&bytes);
1004        assert!(result.is_err());
1005    }
1006
1007    #[test]
1008    fn test_simple_name_extraction() {
1009        assert_eq!(extract_simple_name("java.util.HashMap"), "HashMap");
1010        assert_eq!(extract_simple_name("SimpleClass"), "SimpleClass");
1011        assert_eq!(extract_simple_name("java.util.Map.Entry"), "Entry");
1012    }
1013
1014    #[test]
1015    fn test_no_superclass_for_object() {
1016        let builder = ClassFileBuilder::new("java/lang/Object").no_superclass();
1017        let bytes = builder.build();
1018        let stub = parse_class(&bytes).unwrap();
1019        assert!(stub.superclass.is_none());
1020    }
1021
1022    #[test]
1023    fn test_constructor_and_static_init() {
1024        let mut builder = ClassFileBuilder::new("com/example/WithInit");
1025        builder.add_method("<init>", "()V", 0x0001); // public constructor
1026        builder.add_method("<clinit>", "()V", 0x0008); // static initializer
1027
1028        let bytes = builder.build();
1029        let stub = parse_class(&bytes).unwrap();
1030
1031        assert_eq!(stub.methods.len(), 2);
1032        assert_eq!(stub.methods[0].name, "<init>");
1033        assert_eq!(stub.methods[1].name, "<clinit>");
1034    }
1035
1036    #[test]
1037    fn test_field_method_return_type_object() {
1038        let mut builder = ClassFileBuilder::new("com/example/ReturnTypes");
1039        builder.add_method("getList", "()Ljava/util/List;", 0x0001);
1040
1041        let bytes = builder.build();
1042        let stub = parse_class(&bytes).unwrap();
1043
1044        match &stub.methods[0].return_type {
1045            TypeSignature::Class { fqn, .. } => assert_eq!(fqn, "java.util.List"),
1046            other => panic!("Expected Class return type, got {other:?}"),
1047        }
1048    }
1049}