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