1use crate::stub::model::KotlinMetadataStub;
21
22#[derive(Debug, Clone, PartialEq, Eq)]
32pub struct KotlinClassMetadata {
33 pub kind: KotlinClassKind,
35 pub visibility: KotlinVisibility,
37 pub is_data: bool,
39 pub is_sealed: bool,
41 pub companion_object_name: Option<String>,
46 pub extension_functions: Vec<KotlinExtensionFunction>,
48 pub nullable_properties: Vec<String>,
50}
51
52#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
54pub enum KotlinClassKind {
55 Class,
57 Interface,
59 EnumClass,
61 EnumEntry,
63 AnnotationClass,
65 Object,
67 CompanionObject,
69}
70
71#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
73pub enum KotlinVisibility {
74 Internal,
76 Private,
78 Protected,
80 Public,
82 PrivateToThis,
84 Local,
86}
87
88#[derive(Debug, Clone, PartialEq, Eq)]
90pub struct KotlinExtensionFunction {
91 pub name: String,
93 pub receiver_type: String,
95 pub receiver_nullable: bool,
97}
98
99const WIRE_VARINT: u8 = 0;
105const WIRE_64BIT: u8 = 1;
106const WIRE_LENGTH_DELIMITED: u8 = 2;
107const WIRE_32BIT: u8 = 5;
108
109const CLASS_FLAGS: u32 = 1;
111const CLASS_FUNCTIONS: u32 = 14;
112const CLASS_PROPERTIES: u32 = 15;
113const CLASS_COMPANION_OBJECT_NAME: u32 = 20;
114
115const FUNCTION_FLAGS: u32 = 1;
117const FUNCTION_NAME: u32 = 2;
118const FUNCTION_RECEIVER_TYPE: u32 = 6;
119
120const PROPERTY_FLAGS: u32 = 1;
122const PROPERTY_NAME: u32 = 2;
123const PROPERTY_RETURN_TYPE: u32 = 5;
124
125const TYPE_FLAGS: u32 = 1;
127const TYPE_CLASS_NAME: u32 = 6;
128
129fn extract_visibility(flags: u64) -> KotlinVisibility {
135 match (flags >> 3) & 0x7 {
136 0 => KotlinVisibility::Internal,
137 1 => KotlinVisibility::Private,
138 2 => KotlinVisibility::Protected,
139 3 => KotlinVisibility::Public,
140 4 => KotlinVisibility::PrivateToThis,
141 5 => KotlinVisibility::Local,
142 _ => KotlinVisibility::Public, }
144}
145
146fn extract_modality(flags: u64) -> u8 {
148 ((flags >> 6) & 0x7) as u8
149}
150
151fn extract_class_kind(flags: u64) -> KotlinClassKind {
153 match (flags >> 9) & 0x7 {
154 0 => KotlinClassKind::Class,
155 1 => KotlinClassKind::Interface,
156 2 => KotlinClassKind::EnumClass,
157 3 => KotlinClassKind::EnumEntry,
158 4 => KotlinClassKind::AnnotationClass,
159 5 => KotlinClassKind::Object,
160 6 => KotlinClassKind::CompanionObject,
161 _ => KotlinClassKind::Class, }
163}
164
165struct WireReader<'a> {
174 data: &'a [u8],
175 pos: usize,
176}
177
178impl<'a> WireReader<'a> {
179 fn new(data: &'a [u8]) -> Self {
181 Self { data, pos: 0 }
182 }
183
184 fn has_remaining(&self) -> bool {
186 self.pos < self.data.len()
187 }
188
189 fn read_varint(&mut self) -> Option<u64> {
194 let mut result: u64 = 0;
195 let mut shift: u32 = 0;
196
197 for _ in 0..10 {
198 if self.pos >= self.data.len() {
199 return None;
200 }
201 let byte = self.data[self.pos];
202 self.pos += 1;
203
204 result |= u64::from(byte & 0x7F) << shift;
205 if byte & 0x80 == 0 {
206 return Some(result);
207 }
208 shift += 7;
209 }
210
211 None
213 }
214
215 fn read_tag(&mut self) -> Option<(u32, u8)> {
219 let raw = self.read_varint()?;
220 let wire_type = (raw & 0x7) as u8;
221 let field_number = (raw >> 3) as u32;
222 if field_number == 0 {
223 return None; }
225 Some((field_number, wire_type))
226 }
227
228 fn read_length_delimited(&mut self) -> Option<&'a [u8]> {
232 let len = self.read_varint()? as usize;
233 if self.pos + len > self.data.len() {
234 return None;
235 }
236 let slice = &self.data[self.pos..self.pos + len];
237 self.pos += len;
238 Some(slice)
239 }
240
241 fn skip_field(&mut self, wire_type: u8) -> bool {
245 match wire_type {
246 WIRE_VARINT => self.read_varint().is_some(),
247 WIRE_64BIT => {
248 if self.pos + 8 > self.data.len() {
249 return false;
250 }
251 self.pos += 8;
252 true
253 }
254 WIRE_LENGTH_DELIMITED => self.read_length_delimited().is_some(),
255 WIRE_32BIT => {
256 if self.pos + 4 > self.data.len() {
257 return false;
258 }
259 self.pos += 4;
260 true
261 }
262 _ => false, }
264 }
265}
266
267fn string_table_lookup(d2: &[String], index: u64) -> Option<&str> {
275 let idx = index as usize;
276 d2.get(idx).map(String::as_str)
277}
278
279struct DecodedType {
285 nullable: bool,
287 class_name_index: Option<u64>,
289}
290
291fn decode_type(data: &[u8]) -> Option<DecodedType> {
296 let mut reader = WireReader::new(data);
297 let mut flags: u64 = 0;
298 let mut class_name_index: Option<u64> = None;
299
300 while reader.has_remaining() {
301 let (field_number, wire_type) = reader.read_tag()?;
302
303 match (field_number, wire_type) {
304 (TYPE_FLAGS, WIRE_VARINT) => {
305 flags = reader.read_varint()?;
306 }
307 (TYPE_CLASS_NAME, WIRE_VARINT) => {
308 class_name_index = Some(reader.read_varint()?);
309 }
310 _ => {
311 if !reader.skip_field(wire_type) {
312 return None;
313 }
314 }
315 }
316 }
317
318 Some(DecodedType {
319 nullable: (flags >> 1) & 1 == 1,
320 class_name_index,
321 })
322}
323
324struct DecodedFunction {
330 name_index: u64,
332 receiver_type: Option<DecodedType>,
334}
335
336fn decode_function(data: &[u8]) -> Option<DecodedFunction> {
341 let mut reader = WireReader::new(data);
342 let mut name_index: u64 = 0;
343 let mut receiver_type: Option<DecodedType> = None;
344
345 while reader.has_remaining() {
346 let (field_number, wire_type) = reader.read_tag()?;
347
348 match (field_number, wire_type) {
349 (FUNCTION_FLAGS, WIRE_VARINT) => {
350 let _flags = reader.read_varint()?;
352 }
353 (FUNCTION_NAME, WIRE_VARINT) => {
354 name_index = reader.read_varint()?;
355 }
356 (FUNCTION_RECEIVER_TYPE, WIRE_LENGTH_DELIMITED) => {
357 let type_data = reader.read_length_delimited()?;
358 receiver_type = decode_type(type_data);
359 }
360 _ => {
361 if !reader.skip_field(wire_type) {
362 return None;
363 }
364 }
365 }
366 }
367
368 Some(DecodedFunction {
369 name_index,
370 receiver_type,
371 })
372}
373
374struct DecodedProperty {
380 name_index: u64,
382 return_type_nullable: bool,
384}
385
386fn decode_property(data: &[u8]) -> Option<DecodedProperty> {
390 let mut reader = WireReader::new(data);
391 let mut name_index: u64 = 0;
392 let mut return_type_nullable = false;
393
394 while reader.has_remaining() {
395 let (field_number, wire_type) = reader.read_tag()?;
396
397 match (field_number, wire_type) {
398 (PROPERTY_FLAGS, WIRE_VARINT) => {
399 let _flags = reader.read_varint()?;
400 }
401 (PROPERTY_NAME, WIRE_VARINT) => {
402 name_index = reader.read_varint()?;
403 }
404 (PROPERTY_RETURN_TYPE, WIRE_LENGTH_DELIMITED) => {
405 let type_data = reader.read_length_delimited()?;
406 if let Some(decoded) = decode_type(type_data) {
407 return_type_nullable = decoded.nullable;
408 }
409 }
410 _ => {
411 if !reader.skip_field(wire_type) {
412 return None;
413 }
414 }
415 }
416 }
417
418 Some(DecodedProperty {
419 name_index,
420 return_type_nullable,
421 })
422}
423
424fn decode_class_message(data: &[u8], string_table: &[String]) -> Option<KotlinClassMetadata> {
433 let mut reader = WireReader::new(data);
434 let mut flags: u64 = 0;
435 let mut companion_name_index: Option<u64> = None;
436 let mut extension_functions = Vec::new();
437 let mut nullable_properties = Vec::new();
438
439 while reader.has_remaining() {
440 let (field_number, wire_type) = reader.read_tag()?;
441
442 match (field_number, wire_type) {
443 (CLASS_FLAGS, WIRE_VARINT) => {
444 flags = reader.read_varint()?;
445 }
446 (CLASS_FUNCTIONS, WIRE_LENGTH_DELIMITED) => {
447 let func_data = reader.read_length_delimited()?;
448 if let Some(func) = decode_function(func_data)
449 && let Some(ref recv_type) = func.receiver_type
450 {
451 let fn_name = string_table_lookup(string_table, func.name_index)
453 .unwrap_or("<unknown>")
454 .to_owned();
455
456 let receiver_name = recv_type
457 .class_name_index
458 .and_then(|idx| string_table_lookup(string_table, idx))
459 .unwrap_or("<unknown>")
460 .to_owned();
461
462 extension_functions.push(KotlinExtensionFunction {
463 name: fn_name,
464 receiver_type: receiver_name,
465 receiver_nullable: recv_type.nullable,
466 });
467 }
468 }
469 (CLASS_PROPERTIES, WIRE_LENGTH_DELIMITED) => {
470 let prop_data = reader.read_length_delimited()?;
471 if let Some(prop) = decode_property(prop_data)
472 && prop.return_type_nullable
473 && let Some(name) = string_table_lookup(string_table, prop.name_index)
474 {
475 nullable_properties.push(name.to_owned());
476 }
477 }
478 (CLASS_COMPANION_OBJECT_NAME, WIRE_VARINT) => {
479 companion_name_index = Some(reader.read_varint()?);
480 }
481 _ => {
482 if !reader.skip_field(wire_type) {
483 return None;
484 }
485 }
486 }
487 }
488
489 let class_kind = extract_class_kind(flags);
490 let visibility = extract_visibility(flags);
491 let modality = extract_modality(flags);
492
493 let is_data = class_kind == KotlinClassKind::Class && (flags >> 12) & 1 == 1;
496 let is_sealed = modality == 3; let companion_object_name = companion_name_index
499 .and_then(|idx| string_table_lookup(string_table, idx))
500 .map(str::to_owned);
501
502 Some(KotlinClassMetadata {
503 kind: class_kind,
504 visibility,
505 is_data,
506 is_sealed,
507 companion_object_name,
508 extension_functions,
509 nullable_properties,
510 })
511}
512
513pub fn decode_kotlin_metadata(stub: &KotlinMetadataStub) -> Option<KotlinClassMetadata> {
528 if stub.kind != 1 {
530 log::debug!(
531 "skipping Kotlin metadata kind {} (only kind=1 Class is supported)",
532 stub.kind,
533 );
534 return None;
535 }
536
537 if let Some(&major) = stub.metadata_version.first()
539 && !(1..=2).contains(&major)
540 {
541 log::warn!(
542 "unsupported Kotlin metadata version {:?}, skipping",
543 stub.metadata_version,
544 );
545 return None;
546 }
547
548 let d1_combined = combine_d1_chunks(&stub.data1)?;
550
551 decode_class_message(&d1_combined, &stub.data2)
553}
554
555fn combine_d1_chunks(d1: &[String]) -> Option<Vec<u8>> {
565 if d1.is_empty() {
566 return None;
567 }
568
569 let total_chars: usize = d1.iter().map(|s| s.chars().count()).sum();
570 let mut bytes = Vec::with_capacity(total_chars);
571 for chunk in d1 {
572 for ch in chunk.chars() {
573 bytes.push((ch as u32 & 0xFF) as u8);
577 }
578 }
579 Some(bytes)
580}
581
582#[cfg(test)]
587mod tests {
588 use super::*;
589
590 fn encode_varint(mut value: u64) -> Vec<u8> {
594 let mut buf = Vec::new();
595 loop {
596 let mut byte = (value & 0x7F) as u8;
597 value >>= 7;
598 if value != 0 {
599 byte |= 0x80;
600 }
601 buf.push(byte);
602 if value == 0 {
603 break;
604 }
605 }
606 buf
607 }
608
609 fn encode_tag(field_number: u32, wire_type: u8) -> Vec<u8> {
611 encode_varint(u64::from(field_number) << 3 | u64::from(wire_type))
612 }
613
614 fn encode_varint_field(field_number: u32, value: u64) -> Vec<u8> {
616 let mut buf = encode_tag(field_number, WIRE_VARINT);
617 buf.extend(encode_varint(value));
618 buf
619 }
620
621 fn encode_length_delimited_field(field_number: u32, data: &[u8]) -> Vec<u8> {
623 let mut buf = encode_tag(field_number, WIRE_LENGTH_DELIMITED);
624 buf.extend(encode_varint(data.len() as u64));
625 buf.extend(data);
626 buf
627 }
628
629 fn build_class_flags(visibility: u64, modality: u64, class_kind: u64, is_data: bool) -> u64 {
631 let mut flags = 0u64;
632 flags |= visibility << 3;
633 flags |= modality << 6;
634 flags |= class_kind << 9;
635 if is_data {
636 flags |= 1 << 12;
637 }
638 flags
639 }
640
641 fn build_type_message(nullable: bool, class_name_index: Option<u64>) -> Vec<u8> {
643 let mut buf = Vec::new();
644 let mut flags: u64 = 0;
645 if nullable {
646 flags |= 1 << 1;
647 }
648 if flags != 0 {
649 buf.extend(encode_varint_field(TYPE_FLAGS, flags));
650 }
651 if let Some(idx) = class_name_index {
652 buf.extend(encode_varint_field(TYPE_CLASS_NAME, idx));
653 }
654 buf
655 }
656
657 fn build_function_message(name_index: u64, receiver_type: Option<&[u8]>) -> Vec<u8> {
659 let mut buf = Vec::new();
660 buf.extend(encode_varint_field(FUNCTION_FLAGS, 0));
662 buf.extend(encode_varint_field(FUNCTION_NAME, name_index));
664 if let Some(rt) = receiver_type {
666 buf.extend(encode_length_delimited_field(FUNCTION_RECEIVER_TYPE, rt));
667 }
668 buf
669 }
670
671 fn build_property_message(name_index: u64, return_type: Option<&[u8]>) -> Vec<u8> {
673 let mut buf = Vec::new();
674 buf.extend(encode_varint_field(PROPERTY_FLAGS, 0));
675 buf.extend(encode_varint_field(PROPERTY_NAME, name_index));
676 if let Some(rt) = return_type {
677 buf.extend(encode_length_delimited_field(PROPERTY_RETURN_TYPE, rt));
678 }
679 buf
680 }
681
682 fn make_stub(d1_bytes: Vec<u8>, string_table: Vec<&str>) -> KotlinMetadataStub {
687 let d1_string: String = d1_bytes.iter().map(|&b| b as char).collect();
688 KotlinMetadataStub {
689 kind: 1,
690 metadata_version: vec![1, 9, 0],
691 data1: vec![d1_string],
692 data2: string_table.into_iter().map(str::to_owned).collect(),
693 extra_string: None,
694 package_name: None,
695 extra_int: None,
696 }
697 }
698
699 #[test]
702 fn wire_reader_varint_single_byte() {
703 let data = [0x05]; let mut reader = WireReader::new(&data);
705 assert_eq!(reader.read_varint(), Some(5));
706 assert!(!reader.has_remaining());
707 }
708
709 #[test]
710 fn wire_reader_varint_multi_byte() {
711 let data = [0xAC, 0x02];
715 let mut reader = WireReader::new(&data);
716 assert_eq!(reader.read_varint(), Some(300));
717 }
718
719 #[test]
720 fn wire_reader_varint_max_bytes() {
721 let encoded = encode_varint(u64::MAX);
723 assert_eq!(encoded.len(), 10);
724 let mut reader = WireReader::new(&encoded);
725 assert_eq!(reader.read_varint(), Some(u64::MAX));
726 }
727
728 #[test]
729 fn wire_reader_varint_truncated() {
730 let data = [0x80];
732 let mut reader = WireReader::new(&data);
733 assert_eq!(reader.read_varint(), None);
734 }
735
736 #[test]
737 fn wire_reader_varint_exceeds_10_bytes() {
738 let data = [0x80; 11];
740 let mut reader = WireReader::new(&data);
741 assert_eq!(reader.read_varint(), None);
742 }
743
744 #[test]
745 fn wire_reader_tag_decomposition() {
746 let data = [0x1A];
748 let mut reader = WireReader::new(&data);
749 assert_eq!(reader.read_tag(), Some((3, 2)));
750 }
751
752 #[test]
753 fn wire_reader_tag_field_zero_invalid() {
754 let data = [0x02]; let mut reader = WireReader::new(&data);
757 assert_eq!(reader.read_tag(), None);
758 }
759
760 #[test]
761 fn wire_reader_length_delimited() {
762 let data = [0x0A, 0x03, 0x01, 0x02, 0x03];
765 let mut reader = WireReader::new(&data);
766 let (field, wire) = reader.read_tag().unwrap();
767 assert_eq!((field, wire), (1, 2));
768 let payload = reader.read_length_delimited().unwrap();
769 assert_eq!(payload, &[0x01, 0x02, 0x03]);
770 }
771
772 #[test]
773 fn wire_reader_length_delimited_truncated() {
774 let data = [0x05, 0x01, 0x02];
776 let mut reader = WireReader::new(&data);
777 assert_eq!(reader.read_length_delimited(), None);
778 }
779
780 #[test]
781 fn wire_reader_skip_varint() {
782 let mut data = encode_tag(99, WIRE_VARINT);
783 data.extend(encode_varint(42));
784 data.extend(encode_tag(1, WIRE_VARINT));
785 data.extend(encode_varint(7));
786
787 let mut reader = WireReader::new(&data);
788 let (field, wire) = reader.read_tag().unwrap();
789 assert_eq!(field, 99);
790 assert!(reader.skip_field(wire));
791
792 let (field2, _) = reader.read_tag().unwrap();
793 assert_eq!(field2, 1);
794 }
795
796 #[test]
797 fn wire_reader_skip_32bit() {
798 let mut data = vec![];
799 data.extend(encode_tag(5, WIRE_32BIT));
800 data.extend(&[0x00, 0x00, 0x00, 0x00]); data.extend(encode_tag(1, WIRE_VARINT));
802 data.extend(encode_varint(99));
803
804 let mut reader = WireReader::new(&data);
805 let (_, wire) = reader.read_tag().unwrap();
806 assert!(reader.skip_field(wire));
807 let (field, _) = reader.read_tag().unwrap();
808 assert_eq!(field, 1);
809 }
810
811 #[test]
812 fn wire_reader_skip_64bit() {
813 let mut data = vec![];
814 data.extend(encode_tag(5, WIRE_64BIT));
815 data.extend(&[0u8; 8]); data.extend(encode_tag(1, WIRE_VARINT));
817 data.extend(encode_varint(99));
818
819 let mut reader = WireReader::new(&data);
820 let (_, wire) = reader.read_tag().unwrap();
821 assert!(reader.skip_field(wire));
822 let (field, _) = reader.read_tag().unwrap();
823 assert_eq!(field, 1);
824 }
825
826 #[test]
827 fn wire_reader_skip_unknown_wire_type() {
828 let mut reader = WireReader::new(&[]);
829 assert!(!reader.skip_field(3)); }
831
832 #[test]
835 fn string_table_valid_lookup() {
836 let table = vec!["kotlin/String".to_owned(), "isEmail".to_owned()];
837 assert_eq!(string_table_lookup(&table, 0), Some("kotlin/String"));
838 assert_eq!(string_table_lookup(&table, 1), Some("isEmail"));
839 }
840
841 #[test]
842 fn string_table_out_of_bounds() {
843 let table = vec!["only_one".to_owned()];
844 assert_eq!(string_table_lookup(&table, 1), None);
845 assert_eq!(string_table_lookup(&table, 999), None);
846 }
847
848 #[test]
851 fn extension_receiver_detection() {
852 let receiver_type = build_type_message(false, Some(0));
854 let func = build_function_message(1, Some(&receiver_type));
855
856 let mut d1 = Vec::new();
857 d1.extend(encode_varint_field(
859 CLASS_FLAGS,
860 build_class_flags(3, 0, 0, false),
861 ));
862 d1.extend(encode_length_delimited_field(CLASS_FUNCTIONS, &func));
864
865 let stub = make_stub(d1, vec!["kotlin/String", "isEmail"]);
866 let meta = decode_kotlin_metadata(&stub).unwrap();
867
868 assert_eq!(meta.extension_functions.len(), 1);
869 assert_eq!(meta.extension_functions[0].name, "isEmail");
870 assert_eq!(meta.extension_functions[0].receiver_type, "kotlin/String");
871 assert!(!meta.extension_functions[0].receiver_nullable);
872 }
873
874 #[test]
875 fn extension_receiver_nullable() {
876 let receiver_type = build_type_message(true, Some(0));
878 let func = build_function_message(1, Some(&receiver_type));
879
880 let mut d1 = Vec::new();
881 d1.extend(encode_varint_field(
882 CLASS_FLAGS,
883 build_class_flags(3, 0, 0, false),
884 ));
885 d1.extend(encode_length_delimited_field(CLASS_FUNCTIONS, &func));
886
887 let stub = make_stub(d1, vec!["kotlin/String", "isNullOrEmail"]);
888 let meta = decode_kotlin_metadata(&stub).unwrap();
889
890 assert_eq!(meta.extension_functions.len(), 1);
891 assert_eq!(meta.extension_functions[0].name, "isNullOrEmail");
892 assert!(meta.extension_functions[0].receiver_nullable);
893 }
894
895 #[test]
896 fn regular_function_not_treated_as_extension() {
897 let func = build_function_message(0, None);
899
900 let mut d1 = Vec::new();
901 d1.extend(encode_varint_field(
902 CLASS_FLAGS,
903 build_class_flags(3, 0, 0, false),
904 ));
905 d1.extend(encode_length_delimited_field(CLASS_FUNCTIONS, &func));
906
907 let stub = make_stub(d1, vec!["regularFunction"]);
908 let meta = decode_kotlin_metadata(&stub).unwrap();
909
910 assert!(meta.extension_functions.is_empty());
911 }
912
913 #[test]
916 fn nullable_property_detection() {
917 let nullable_type = build_type_message(true, Some(0));
919 let prop = build_property_message(1, Some(&nullable_type));
920
921 let mut d1 = Vec::new();
922 d1.extend(encode_varint_field(
923 CLASS_FLAGS,
924 build_class_flags(3, 0, 0, false),
925 ));
926 d1.extend(encode_length_delimited_field(CLASS_PROPERTIES, &prop));
927
928 let stub = make_stub(d1, vec!["kotlin/String", "name"]);
929 let meta = decode_kotlin_metadata(&stub).unwrap();
930
931 assert_eq!(meta.nullable_properties, vec!["name"]);
932 }
933
934 #[test]
935 fn non_nullable_property_not_included() {
936 let non_nullable_type = build_type_message(false, Some(0));
937 let prop = build_property_message(1, Some(&non_nullable_type));
938
939 let mut d1 = Vec::new();
940 d1.extend(encode_varint_field(
941 CLASS_FLAGS,
942 build_class_flags(3, 0, 0, false),
943 ));
944 d1.extend(encode_length_delimited_field(CLASS_PROPERTIES, &prop));
945
946 let stub = make_stub(d1, vec!["kotlin/String", "name"]);
947 let meta = decode_kotlin_metadata(&stub).unwrap();
948
949 assert!(meta.nullable_properties.is_empty());
950 }
951
952 #[test]
955 fn companion_object_detection() {
956 let mut d1 = Vec::new();
957 d1.extend(encode_varint_field(
958 CLASS_FLAGS,
959 build_class_flags(3, 0, 0, false),
960 ));
961 d1.extend(encode_varint_field(CLASS_COMPANION_OBJECT_NAME, 0));
963
964 let stub = make_stub(d1, vec!["Companion"]);
965 let meta = decode_kotlin_metadata(&stub).unwrap();
966
967 assert_eq!(meta.companion_object_name, Some("Companion".to_owned()));
968 }
969
970 #[test]
971 fn companion_object_custom_name() {
972 let mut d1 = Vec::new();
973 d1.extend(encode_varint_field(
974 CLASS_FLAGS,
975 build_class_flags(3, 0, 0, false),
976 ));
977 d1.extend(encode_varint_field(CLASS_COMPANION_OBJECT_NAME, 0));
978
979 let stub = make_stub(d1, vec!["Factory"]);
980 let meta = decode_kotlin_metadata(&stub).unwrap();
981
982 assert_eq!(meta.companion_object_name, Some("Factory".to_owned()));
983 }
984
985 #[test]
986 fn no_companion_object() {
987 let mut d1 = Vec::new();
988 d1.extend(encode_varint_field(
989 CLASS_FLAGS,
990 build_class_flags(3, 0, 0, false),
991 ));
992
993 let stub = make_stub(d1, vec![]);
994 let meta = decode_kotlin_metadata(&stub).unwrap();
995
996 assert_eq!(meta.companion_object_name, None);
997 }
998
999 #[test]
1002 fn object_declaration_kind() {
1003 let mut d1 = Vec::new();
1004 d1.extend(encode_varint_field(
1006 CLASS_FLAGS,
1007 build_class_flags(3, 0, 5, false),
1008 ));
1009
1010 let stub = make_stub(d1, vec![]);
1011 let meta = decode_kotlin_metadata(&stub).unwrap();
1012
1013 assert_eq!(meta.kind, KotlinClassKind::Object);
1014 }
1015
1016 #[test]
1017 fn companion_object_kind() {
1018 let mut d1 = Vec::new();
1019 d1.extend(encode_varint_field(
1021 CLASS_FLAGS,
1022 build_class_flags(3, 0, 6, false),
1023 ));
1024
1025 let stub = make_stub(d1, vec![]);
1026 let meta = decode_kotlin_metadata(&stub).unwrap();
1027
1028 assert_eq!(meta.kind, KotlinClassKind::CompanionObject);
1029 }
1030
1031 #[test]
1034 fn data_class_detection() {
1035 let mut d1 = Vec::new();
1036 d1.extend(encode_varint_field(
1038 CLASS_FLAGS,
1039 build_class_flags(3, 0, 0, true),
1040 ));
1041
1042 let stub = make_stub(d1, vec![]);
1043 let meta = decode_kotlin_metadata(&stub).unwrap();
1044
1045 assert!(meta.is_data);
1046 assert_eq!(meta.kind, KotlinClassKind::Class);
1047 }
1048
1049 #[test]
1050 fn non_data_class() {
1051 let mut d1 = Vec::new();
1052 d1.extend(encode_varint_field(
1053 CLASS_FLAGS,
1054 build_class_flags(3, 0, 0, false),
1055 ));
1056
1057 let stub = make_stub(d1, vec![]);
1058 let meta = decode_kotlin_metadata(&stub).unwrap();
1059
1060 assert!(!meta.is_data);
1061 }
1062
1063 #[test]
1066 fn sealed_class_detection() {
1067 let mut d1 = Vec::new();
1068 d1.extend(encode_varint_field(
1070 CLASS_FLAGS,
1071 build_class_flags(3, 3, 0, false),
1072 ));
1073
1074 let stub = make_stub(d1, vec![]);
1075 let meta = decode_kotlin_metadata(&stub).unwrap();
1076
1077 assert!(meta.is_sealed);
1078 }
1079
1080 #[test]
1081 fn non_sealed_class() {
1082 let mut d1 = Vec::new();
1083 d1.extend(encode_varint_field(
1085 CLASS_FLAGS,
1086 build_class_flags(3, 0, 0, false),
1087 ));
1088
1089 let stub = make_stub(d1, vec![]);
1090 let meta = decode_kotlin_metadata(&stub).unwrap();
1091
1092 assert!(!meta.is_sealed);
1093 }
1094
1095 #[test]
1098 fn visibility_public() {
1099 let mut d1 = Vec::new();
1100 d1.extend(encode_varint_field(
1101 CLASS_FLAGS,
1102 build_class_flags(3, 0, 0, false), ));
1104
1105 let stub = make_stub(d1, vec![]);
1106 let meta = decode_kotlin_metadata(&stub).unwrap();
1107 assert_eq!(meta.visibility, KotlinVisibility::Public);
1108 }
1109
1110 #[test]
1111 fn visibility_private() {
1112 let mut d1 = Vec::new();
1113 d1.extend(encode_varint_field(
1114 CLASS_FLAGS,
1115 build_class_flags(1, 0, 0, false), ));
1117
1118 let stub = make_stub(d1, vec![]);
1119 let meta = decode_kotlin_metadata(&stub).unwrap();
1120 assert_eq!(meta.visibility, KotlinVisibility::Private);
1121 }
1122
1123 #[test]
1124 fn visibility_internal() {
1125 let mut d1 = Vec::new();
1126 d1.extend(encode_varint_field(
1127 CLASS_FLAGS,
1128 build_class_flags(0, 0, 0, false), ));
1130
1131 let stub = make_stub(d1, vec![]);
1132 let meta = decode_kotlin_metadata(&stub).unwrap();
1133 assert_eq!(meta.visibility, KotlinVisibility::Internal);
1134 }
1135
1136 #[test]
1137 fn visibility_protected() {
1138 let mut d1 = Vec::new();
1139 d1.extend(encode_varint_field(
1140 CLASS_FLAGS,
1141 build_class_flags(2, 0, 0, false), ));
1143
1144 let stub = make_stub(d1, vec![]);
1145 let meta = decode_kotlin_metadata(&stub).unwrap();
1146 assert_eq!(meta.visibility, KotlinVisibility::Protected);
1147 }
1148
1149 #[test]
1152 fn malformed_protobuf_returns_none() {
1153 let stub = make_stub(
1156 vec![
1157 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
1158 ],
1159 vec![],
1160 );
1161 let _result = decode_kotlin_metadata(&stub);
1164 }
1165
1166 #[test]
1167 fn empty_d1_returns_none() {
1168 let stub = KotlinMetadataStub {
1169 kind: 1,
1170 metadata_version: vec![1, 9, 0],
1171 data1: vec![],
1172 data2: vec![],
1173 extra_string: None,
1174 package_name: None,
1175 extra_int: None,
1176 };
1177 assert_eq!(decode_kotlin_metadata(&stub), None);
1178 }
1179
1180 #[test]
1181 fn truncated_varint_returns_none() {
1182 let stub = make_stub(vec![0x80], vec![]);
1184 assert_eq!(decode_kotlin_metadata(&stub), None);
1185 }
1186
1187 #[test]
1190 fn unsupported_kind_returns_none() {
1191 let stub = KotlinMetadataStub {
1192 kind: 2, metadata_version: vec![1, 9, 0],
1194 data1: vec!["data".to_owned()],
1195 data2: vec![],
1196 extra_string: None,
1197 package_name: None,
1198 extra_int: None,
1199 };
1200 assert_eq!(decode_kotlin_metadata(&stub), None);
1201 }
1202
1203 #[test]
1204 fn unsupported_kind_synthetic() {
1205 let stub = KotlinMetadataStub {
1206 kind: 3,
1207 metadata_version: vec![1, 9, 0],
1208 data1: vec![],
1209 data2: vec![],
1210 extra_string: None,
1211 package_name: None,
1212 extra_int: None,
1213 };
1214 assert_eq!(decode_kotlin_metadata(&stub), None);
1215 }
1216
1217 #[test]
1218 fn unsupported_metadata_version() {
1219 let stub = KotlinMetadataStub {
1220 kind: 1,
1221 metadata_version: vec![99, 0, 0], data1: vec!["data".to_owned()],
1223 data2: vec![],
1224 extra_string: None,
1225 package_name: None,
1226 extra_int: None,
1227 };
1228 assert_eq!(decode_kotlin_metadata(&stub), None);
1229 }
1230
1231 #[test]
1234 fn comprehensive_class_decoding() {
1235 let string_table = vec!["kotlin/String", "isEmail", "name", "Companion", "toString"];
1242
1243 let receiver_type = build_type_message(false, Some(0));
1245 let ext_func = build_function_message(1, Some(&receiver_type));
1246
1247 let regular_func = build_function_message(4, None);
1249
1250 let nullable_type = build_type_message(true, Some(0));
1252 let nullable_prop = build_property_message(2, Some(&nullable_type));
1253
1254 let mut d1 = Vec::new();
1256 d1.extend(encode_varint_field(
1258 CLASS_FLAGS,
1259 build_class_flags(3, 0, 0, false),
1260 ));
1261 d1.extend(encode_length_delimited_field(CLASS_FUNCTIONS, &ext_func));
1262 d1.extend(encode_length_delimited_field(
1263 CLASS_FUNCTIONS,
1264 ®ular_func,
1265 ));
1266 d1.extend(encode_length_delimited_field(
1267 CLASS_PROPERTIES,
1268 &nullable_prop,
1269 ));
1270 d1.extend(encode_varint_field(CLASS_COMPANION_OBJECT_NAME, 3));
1271
1272 let stub = make_stub(d1, string_table);
1273 let meta = decode_kotlin_metadata(&stub).unwrap();
1274
1275 assert_eq!(meta.kind, KotlinClassKind::Class);
1276 assert_eq!(meta.visibility, KotlinVisibility::Public);
1277 assert!(!meta.is_data);
1278 assert!(!meta.is_sealed);
1279 assert_eq!(meta.companion_object_name, Some("Companion".to_owned()));
1280 assert_eq!(meta.extension_functions.len(), 1);
1281 assert_eq!(meta.extension_functions[0].name, "isEmail");
1282 assert_eq!(meta.extension_functions[0].receiver_type, "kotlin/String");
1283 assert_eq!(meta.nullable_properties, vec!["name"]);
1284 }
1285
1286 #[test]
1289 fn interface_kind_detection() {
1290 let mut d1 = Vec::new();
1291 d1.extend(encode_varint_field(
1293 CLASS_FLAGS,
1294 build_class_flags(3, 2, 1, false), ));
1296
1297 let stub = make_stub(d1, vec![]);
1298 let meta = decode_kotlin_metadata(&stub).unwrap();
1299
1300 assert_eq!(meta.kind, KotlinClassKind::Interface);
1301 }
1302
1303 #[test]
1306 fn enum_class_kind_detection() {
1307 let mut d1 = Vec::new();
1308 d1.extend(encode_varint_field(
1310 CLASS_FLAGS,
1311 build_class_flags(3, 0, 2, false),
1312 ));
1313
1314 let stub = make_stub(d1, vec![]);
1315 let meta = decode_kotlin_metadata(&stub).unwrap();
1316
1317 assert_eq!(meta.kind, KotlinClassKind::EnumClass);
1318 }
1319
1320 #[test]
1323 fn annotation_class_kind_detection() {
1324 let mut d1 = Vec::new();
1325 d1.extend(encode_varint_field(
1327 CLASS_FLAGS,
1328 build_class_flags(3, 0, 4, false),
1329 ));
1330
1331 let stub = make_stub(d1, vec![]);
1332 let meta = decode_kotlin_metadata(&stub).unwrap();
1333
1334 assert_eq!(meta.kind, KotlinClassKind::AnnotationClass);
1335 }
1336
1337 #[test]
1340 fn multiple_extension_functions() {
1341 let recv_string = build_type_message(false, Some(0));
1342 let recv_list = build_type_message(false, Some(2));
1343
1344 let func1 = build_function_message(1, Some(&recv_string));
1345 let func2 = build_function_message(3, Some(&recv_list));
1346
1347 let mut d1 = Vec::new();
1348 d1.extend(encode_varint_field(
1349 CLASS_FLAGS,
1350 build_class_flags(3, 0, 0, false),
1351 ));
1352 d1.extend(encode_length_delimited_field(CLASS_FUNCTIONS, &func1));
1353 d1.extend(encode_length_delimited_field(CLASS_FUNCTIONS, &func2));
1354
1355 let stub = make_stub(
1356 d1,
1357 vec!["kotlin/String", "isEmail", "kotlin/List", "firstOrNull"],
1358 );
1359 let meta = decode_kotlin_metadata(&stub).unwrap();
1360
1361 assert_eq!(meta.extension_functions.len(), 2);
1362 assert_eq!(meta.extension_functions[0].name, "isEmail");
1363 assert_eq!(meta.extension_functions[0].receiver_type, "kotlin/String");
1364 assert_eq!(meta.extension_functions[1].name, "firstOrNull");
1365 assert_eq!(meta.extension_functions[1].receiver_type, "kotlin/List");
1366 }
1367
1368 #[test]
1371 fn multiple_nullable_properties() {
1372 let nullable_type = build_type_message(true, Some(0));
1373 let prop1 = build_property_message(1, Some(&nullable_type));
1374 let prop2 = build_property_message(2, Some(&nullable_type));
1375
1376 let mut d1 = Vec::new();
1377 d1.extend(encode_varint_field(
1378 CLASS_FLAGS,
1379 build_class_flags(3, 0, 0, false),
1380 ));
1381 d1.extend(encode_length_delimited_field(CLASS_PROPERTIES, &prop1));
1382 d1.extend(encode_length_delimited_field(CLASS_PROPERTIES, &prop2));
1383
1384 let stub = make_stub(d1, vec!["kotlin/String", "name", "email"]);
1385 let meta = decode_kotlin_metadata(&stub).unwrap();
1386
1387 assert_eq!(meta.nullable_properties.len(), 2);
1388 assert!(meta.nullable_properties.contains(&"name".to_owned()));
1389 assert!(meta.nullable_properties.contains(&"email".to_owned()));
1390 }
1391
1392 #[test]
1395 fn combine_d1_multiple_chunks() {
1396 let d1 = vec!["hel".to_owned(), "lo".to_owned()];
1397 let combined = combine_d1_chunks(&d1).unwrap();
1398 assert_eq!(combined, b"hello");
1399 }
1400
1401 #[test]
1402 fn combine_d1_empty() {
1403 let d1: Vec<String> = vec![];
1404 assert_eq!(combine_d1_chunks(&d1), None);
1405 }
1406
1407 #[test]
1410 fn data_class_with_sealed_is_not_data() {
1411 let mut d1 = Vec::new();
1413 d1.extend(encode_varint_field(
1415 CLASS_FLAGS,
1416 build_class_flags(3, 3, 0, true),
1417 ));
1418
1419 let stub = make_stub(d1, vec![]);
1420 let meta = decode_kotlin_metadata(&stub).unwrap();
1421
1422 assert!(meta.is_data);
1423 assert!(meta.is_sealed);
1424 }
1425
1426 #[test]
1429 fn missing_flags_defaults_to_internal_final_class() {
1430 let d1 = Vec::new();
1432 let stub = make_stub(d1, vec![]);
1433 let meta = decode_kotlin_metadata(&stub).unwrap();
1434
1435 assert_eq!(meta.kind, KotlinClassKind::Class);
1437 assert_eq!(meta.visibility, KotlinVisibility::Internal);
1438 assert!(!meta.is_data);
1439 assert!(!meta.is_sealed);
1440 assert!(meta.companion_object_name.is_none());
1441 assert!(meta.extension_functions.is_empty());
1442 assert!(meta.nullable_properties.is_empty());
1443 }
1444
1445 #[test]
1448 fn visibility_private_to_this() {
1449 let mut d1 = Vec::new();
1450 d1.extend(encode_varint_field(
1451 CLASS_FLAGS,
1452 build_class_flags(4, 0, 0, false),
1453 ));
1454
1455 let stub = make_stub(d1, vec![]);
1456 let meta = decode_kotlin_metadata(&stub).unwrap();
1457 assert_eq!(meta.visibility, KotlinVisibility::PrivateToThis);
1458 }
1459
1460 #[test]
1461 fn visibility_local() {
1462 let mut d1 = Vec::new();
1463 d1.extend(encode_varint_field(
1464 CLASS_FLAGS,
1465 build_class_flags(5, 0, 0, false),
1466 ));
1467
1468 let stub = make_stub(d1, vec![]);
1469 let meta = decode_kotlin_metadata(&stub).unwrap();
1470 assert_eq!(meta.visibility, KotlinVisibility::Local);
1471 }
1472
1473 #[test]
1476 fn decode_type_nullable() {
1477 let data = build_type_message(true, Some(5));
1478 let decoded = decode_type(&data).unwrap();
1479 assert!(decoded.nullable);
1480 assert_eq!(decoded.class_name_index, Some(5));
1481 }
1482
1483 #[test]
1484 fn decode_type_non_nullable() {
1485 let data = build_type_message(false, Some(3));
1486 let decoded = decode_type(&data).unwrap();
1487 assert!(!decoded.nullable);
1488 assert_eq!(decoded.class_name_index, Some(3));
1489 }
1490
1491 #[test]
1492 fn decode_type_no_class_name() {
1493 let data = build_type_message(true, None);
1494 let decoded = decode_type(&data).unwrap();
1495 assert!(decoded.nullable);
1496 assert_eq!(decoded.class_name_index, None);
1497 }
1498
1499 #[test]
1500 fn decode_type_empty() {
1501 let decoded = decode_type(&[]).unwrap();
1502 assert!(!decoded.nullable);
1503 assert_eq!(decoded.class_name_index, None);
1504 }
1505}