1use 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
25const METHOD_ACC_BRIDGE: u16 = 0x0040;
31const METHOD_ACC_SYNTHETIC: u16 = 0x1000;
33
34pub fn parse_class(bytes: &[u8]) -> ClasspathResult<ClassStub> {
51 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 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 let methods = parse_methods(&class_file.methods, &fqn);
90
91 let fields = parse_fields(&class_file.fields, &fqn);
93
94 let enum_constants = if kind == ClassKind::Enum {
96 extract_enum_constants(&class_file.fields, &this_class_raw)
97 } else {
98 vec![]
99 };
100
101 let inner_classes = extract_inner_classes(&class_file.attributes);
103
104 let record_components = extract_record_components(&class_file.attributes);
106
107 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![], generic_signature: None, inner_classes,
122 lambda_targets: vec![], module: None, record_components,
125 enum_constants,
126 source_file,
127 source_jar: None, kotlin_metadata: None, scala_signature: None, })
131}
132
133fn extract_simple_name(fqn: &str) -> String {
142 fqn.rsplit('.').next().unwrap_or(fqn).to_owned()
145}
146
147fn convert_class_access_flags(flags: ClassAccessFlags) -> AccessFlags {
149 AccessFlags::new(flags.bits())
150}
151
152fn convert_method_access_flags(flags: MethodAccessFlags) -> AccessFlags {
154 AccessFlags::new(flags.bits())
155}
156
157fn convert_field_access_flags(flags: FieldAccessFlags) -> AccessFlags {
159 AccessFlags::new(flags.bits())
160}
161
162fn 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 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
197fn 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 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
227fn 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 let parameter_names = extract_method_parameter_names(&method.attributes);
237
238 Ok(MethodStub {
239 name,
240 access,
241 descriptor,
242 generic_signature: None, annotations: vec![], parameter_annotations: vec![], parameter_names,
246 return_type,
247 parameter_types,
248 })
249}
250
251fn 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
274fn 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 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, annotations: vec![], constant_value,
294 })
295}
296
297fn 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 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
324fn 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
351fn 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, annotations: vec![], });
369 }
370 }
371 }
372 result
373}
374
375#[cfg(test)]
380mod tests {
381 use super::*;
382 use crate::stub::model::{BaseType, ConstantValue, TypeSignature};
383
384 struct ClassFileBuilder {
394 major_version: u16,
396 cp_entries: Vec<Vec<u8>>,
398 access_flags: u16,
400 this_class_idx: u16,
402 super_class_idx: u16,
404 interface_indices: Vec<u16>,
406 fields: Vec<Vec<u8>>,
408 methods: Vec<Vec<u8>>,
410 attributes: Vec<Vec<u8>>,
412 }
413
414 impl ClassFileBuilder {
415 fn new(class_name: &str) -> Self {
418 let mut builder = Self {
419 major_version: 52,
420 cp_entries: Vec::new(),
421 access_flags: 0x0021, 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 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 fn add_utf8(&mut self, s: &str) -> u16 {
439 let mut entry = vec![1u8]; 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 fn add_class(&mut self, name_idx: u16) -> u16 {
449 let mut entry = vec![7u8]; 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 fn add_integer(&mut self, value: i32) -> u16 {
457 let mut entry = vec![3u8]; entry.extend_from_slice(&value.to_be_bytes());
459 self.cp_entries.push(entry);
460 self.cp_entries.len() as u16
461 }
462
463 fn add_string(&mut self, utf8_idx: u16) -> u16 {
465 let mut entry = vec![8u8]; 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 fn access_flags(mut self, flags: u16) -> Self {
473 self.access_flags = flags;
474 self
475 }
476
477 fn no_superclass(mut self) -> Self {
479 self.super_class_idx = 0;
480 self
481 }
482
483 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 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 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 let attr_name_idx = self.add_utf8("ConstantValue");
517 field_bytes.extend_from_slice(&1u16.to_be_bytes()); field_bytes.extend_from_slice(&attr_name_idx.to_be_bytes());
519 field_bytes.extend_from_slice(&2u32.to_be_bytes()); field_bytes.extend_from_slice(&cv_idx.to_be_bytes());
521 } else {
522 field_bytes.extend_from_slice(&0u16.to_be_bytes()); }
524
525 self.fields.push(field_bytes);
526 self
527 }
528
529 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()); self.methods.push(method_bytes);
541 self
542 }
543
544 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 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 method_bytes.extend_from_slice(&1u16.to_be_bytes());
567 method_bytes.extend_from_slice(&attr_name_idx.to_be_bytes());
568 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 ¶m_name_indices {
573 method_bytes.extend_from_slice(&idx.to_be_bytes());
574 method_bytes.extend_from_slice(&0u16.to_be_bytes()); }
576
577 self.methods.push(method_bytes);
578 self
579 }
580
581 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 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()); attr_bytes.extend_from_slice(&source_idx.to_be_bytes());
632
633 self.attributes.push(attr_bytes);
634 self
635 }
636
637 fn build(&self) -> Vec<u8> {
639 let mut bytes = Vec::new();
640
641 bytes.extend_from_slice(&0xCAFE_BABEu32.to_be_bytes());
643 bytes.extend_from_slice(&0u16.to_be_bytes());
645 bytes.extend_from_slice(&self.major_version.to_be_bytes());
647
648 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 bytes.extend_from_slice(&self.access_flags.to_be_bytes());
657 bytes.extend_from_slice(&self.this_class_idx.to_be_bytes());
659 bytes.extend_from_slice(&self.super_class_idx.to_be_bytes());
661
662 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 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 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 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 #[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); 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();
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 builder = builder.access_flags(0x0001 | 0x0010 | 0x0020 | 0x4000);
741 builder = builder.superclass("java/lang/Enum");
743
744 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 builder.add_field("rgb", "I", 0x0012, None); 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); 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, ),
782 (
783 "com/example/Outer$1",
784 None, None, 0x0000, ),
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 let int_idx = builder.add_integer(42);
814 builder.add_field("MAX_SIZE", "I", 0x0019, Some(int_idx)); 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 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 assert_eq!(stub.fields[0].name, "MAX_SIZE");
831 assert_eq!(stub.fields[0].constant_value, Some(ConstantValue::Int(42)));
832
833 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 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); builder.add_method("access$000", "()V", METHOD_ACC_SYNTHETIC); builder.add_method("bridge$0", "()V", METHOD_ACC_BRIDGE); 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 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 assert!(parse_class(&[]).is_err());
876
877 assert!(parse_class(&[0xDE, 0xAD, 0xBE, 0xEF]).is_err());
879
880 assert!(parse_class(&[0xCA, 0xFE, 0xBA, 0xBE, 0x00, 0x00, 0x00, 0x34]).is_err());
882
883 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 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 let builder = ClassFileBuilder::new("com/example/Abstract").access_flags(0x0421); 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 let builder = ClassFileBuilder::new("com/example/Final").access_flags(0x0031); 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, &["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 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); builder.add_method("<clinit>", "()V", 0x0008); 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}