1#![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
28const METHOD_ACC_BRIDGE: u16 = 0x0040;
34const METHOD_ACC_SYNTHETIC: u16 = 0x1000;
36
37pub fn parse_class(bytes: &[u8]) -> ClasspathResult<ClassStub> {
54 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 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 let methods = parse_methods(&class_file.methods, &fqn);
93
94 let fields = parse_fields(&class_file.fields, &fqn);
96
97 let enum_constants = if kind == ClassKind::Enum {
99 extract_enum_constants(&class_file.fields, &this_class_raw)
100 } else {
101 vec![]
102 };
103
104 let inner_classes = extract_inner_classes(&class_file.attributes);
106
107 let record_components = extract_record_components(&class_file.attributes);
109
110 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![], generic_signature: None, inner_classes,
125 lambda_targets: vec![], module: None, record_components,
128 enum_constants,
129 source_file,
130 source_jar: None, kotlin_metadata: None, scala_signature: None, })
134}
135
136fn extract_simple_name(fqn: &str) -> String {
145 fqn.rsplit('.').next().unwrap_or(fqn).to_owned()
148}
149
150fn convert_class_access_flags(flags: ClassAccessFlags) -> AccessFlags {
152 AccessFlags::new(flags.bits())
153}
154
155fn convert_method_access_flags(flags: MethodAccessFlags) -> AccessFlags {
157 AccessFlags::new(flags.bits())
158}
159
160fn convert_field_access_flags(flags: FieldAccessFlags) -> AccessFlags {
162 AccessFlags::new(flags.bits())
163}
164
165fn 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 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
200fn 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 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#[allow(clippy::unnecessary_debug_formatting)] #[allow(clippy::unnecessary_wraps)] fn 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 let parameter_names = extract_method_parameter_names(&method.attributes);
242
243 Ok(MethodStub {
244 name,
245 access,
246 descriptor,
247 generic_signature: None, annotations: vec![], parameter_annotations: vec![], parameter_names,
251 return_type,
252 parameter_types,
253 })
254}
255
256fn 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#[allow(clippy::unnecessary_wraps)] fn 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 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, annotations: vec![], constant_value,
300 })
301}
302
303fn 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 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
330fn 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
360fn 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, annotations: vec![], });
378 }
379 }
380 }
381 result
382}
383
384#[cfg(test)]
389mod tests {
390 use super::*;
391 use crate::stub::model::{BaseType, ConstantValue, TypeSignature};
392
393 struct ClassFileBuilder {
403 major_version: u16,
405 cp_entries: Vec<Vec<u8>>,
407 access_flags: u16,
409 this_class_idx: u16,
411 super_class_idx: u16,
413 interface_indices: Vec<u16>,
415 fields: Vec<Vec<u8>>,
417 methods: Vec<Vec<u8>>,
419 attributes: Vec<Vec<u8>>,
421 }
422
423 impl ClassFileBuilder {
424 fn new(class_name: &str) -> Self {
427 let mut builder = Self {
428 major_version: 52,
429 cp_entries: Vec::new(),
430 access_flags: 0x0021, 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 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 fn add_utf8(&mut self, s: &str) -> u16 {
448 let mut entry = vec![1u8]; 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 fn add_class(&mut self, name_idx: u16) -> u16 {
458 let mut entry = vec![7u8]; 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 fn add_integer(&mut self, value: i32) -> u16 {
466 let mut entry = vec![3u8]; entry.extend_from_slice(&value.to_be_bytes());
468 self.cp_entries.push(entry);
469 self.cp_entries.len() as u16
470 }
471
472 fn add_string(&mut self, utf8_idx: u16) -> u16 {
474 let mut entry = vec![8u8]; 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 fn access_flags(mut self, flags: u16) -> Self {
482 self.access_flags = flags;
483 self
484 }
485
486 fn no_superclass(mut self) -> Self {
488 self.super_class_idx = 0;
489 self
490 }
491
492 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 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 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 let attr_name_idx = self.add_utf8("ConstantValue");
526 field_bytes.extend_from_slice(&1u16.to_be_bytes()); field_bytes.extend_from_slice(&attr_name_idx.to_be_bytes());
528 field_bytes.extend_from_slice(&2u32.to_be_bytes()); field_bytes.extend_from_slice(&cv_idx.to_be_bytes());
530 } else {
531 field_bytes.extend_from_slice(&0u16.to_be_bytes()); }
533
534 self.fields.push(field_bytes);
535 self
536 }
537
538 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()); self.methods.push(method_bytes);
550 self
551 }
552
553 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 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 method_bytes.extend_from_slice(&1u16.to_be_bytes());
576 method_bytes.extend_from_slice(&attr_name_idx.to_be_bytes());
577 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 ¶m_name_indices {
582 method_bytes.extend_from_slice(&idx.to_be_bytes());
583 method_bytes.extend_from_slice(&0u16.to_be_bytes()); }
585
586 self.methods.push(method_bytes);
587 self
588 }
589
590 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 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()); attr_bytes.extend_from_slice(&source_idx.to_be_bytes());
641
642 self.attributes.push(attr_bytes);
643 self
644 }
645
646 fn build(&self) -> Vec<u8> {
648 let mut bytes = Vec::new();
649
650 bytes.extend_from_slice(&0xCAFE_BABEu32.to_be_bytes());
652 bytes.extend_from_slice(&0u16.to_be_bytes());
654 bytes.extend_from_slice(&self.major_version.to_be_bytes());
656
657 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 bytes.extend_from_slice(&self.access_flags.to_be_bytes());
666 bytes.extend_from_slice(&self.this_class_idx.to_be_bytes());
668 bytes.extend_from_slice(&self.super_class_idx.to_be_bytes());
670
671 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 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 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 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 #[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); builder.add_method("hashCode", "()I", 0x0001); builder.add_field("name", "Ljava/lang/String;", 0x0002, None); builder.add_field("age", "I", 0x0001, None); 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 builder = builder.access_flags(0x0001 | 0x0010 | 0x0020 | 0x4000);
750 builder = builder.superclass("java/lang/Enum");
752
753 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 builder.add_field("rgb", "I", 0x0012, None); 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); 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, ),
791 (
792 "com/example/Outer$1",
793 None, None, 0x0000, ),
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 let int_idx = builder.add_integer(42);
823 builder.add_field("MAX_SIZE", "I", 0x0019, Some(int_idx)); 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 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 assert_eq!(stub.fields[0].name, "MAX_SIZE");
840 assert_eq!(stub.fields[0].constant_value, Some(ConstantValue::Int(42)));
841
842 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 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); builder.add_method("access$000", "()V", METHOD_ACC_SYNTHETIC); builder.add_method("bridge$0", "()V", METHOD_ACC_BRIDGE); 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 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 assert!(parse_class(&[]).is_err());
885
886 assert!(parse_class(&[0xDE, 0xAD, 0xBE, 0xEF]).is_err());
888
889 assert!(parse_class(&[0xCA, 0xFE, 0xBA, 0xBE, 0x00, 0x00, 0x00, 0x34]).is_err());
891
892 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 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 let builder = ClassFileBuilder::new("com/example/Abstract").access_flags(0x0421); 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 let builder = ClassFileBuilder::new("com/example/Final").access_flags(0x0031); 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, &["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 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); builder.add_method("<clinit>", "()V", 0x0008); 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}