1#![allow(clippy::cast_possible_truncation)]
22
23use crate::stub::model::KotlinMetadataStub;
24
25#[derive(Debug, Clone, PartialEq, Eq)]
35pub struct KotlinClassMetadata {
36 pub kind: KotlinClassKind,
38 pub visibility: KotlinVisibility,
40 pub is_data: bool,
42 pub is_sealed: bool,
44 pub companion_object_name: Option<String>,
49 pub extension_functions: Vec<KotlinExtensionFunction>,
51 pub nullable_properties: Vec<String>,
53}
54
55#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
57pub enum KotlinClassKind {
58 Class,
60 Interface,
62 EnumClass,
64 EnumEntry,
66 AnnotationClass,
68 Object,
70 CompanionObject,
72}
73
74#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
76pub enum KotlinVisibility {
77 Internal,
79 Private,
81 Protected,
83 Public,
85 PrivateToThis,
87 Local,
89}
90
91#[derive(Debug, Clone, PartialEq, Eq)]
93pub struct KotlinExtensionFunction {
94 pub name: String,
96 pub receiver_type: String,
98 pub receiver_nullable: bool,
100}
101
102const WIRE_VARINT: u8 = 0;
108const WIRE_64BIT: u8 = 1;
109const WIRE_LENGTH_DELIMITED: u8 = 2;
110const WIRE_32BIT: u8 = 5;
111
112const CLASS_FLAGS: u32 = 1;
114const CLASS_FUNCTIONS: u32 = 14;
115const CLASS_PROPERTIES: u32 = 15;
116const CLASS_COMPANION_OBJECT_NAME: u32 = 20;
117
118const FUNCTION_FLAGS: u32 = 1;
120const FUNCTION_NAME: u32 = 2;
121const FUNCTION_RECEIVER_TYPE: u32 = 6;
122
123const PROPERTY_FLAGS: u32 = 1;
125const PROPERTY_NAME: u32 = 2;
126const PROPERTY_RETURN_TYPE: u32 = 5;
127
128const TYPE_FLAGS: u32 = 1;
130const TYPE_CLASS_NAME: u32 = 6;
131
132fn extract_visibility(flags: u64) -> KotlinVisibility {
138 #[allow(clippy::match_same_arms)] match (flags >> 3) & 0x7 {
140 #[allow(clippy::match_same_arms)] 0 => KotlinVisibility::Internal,
142 1 => KotlinVisibility::Private,
143 2 => KotlinVisibility::Protected,
144 3 => KotlinVisibility::Public,
145 4 => KotlinVisibility::PrivateToThis,
146 5 => KotlinVisibility::Local,
147 _ => KotlinVisibility::Public, }
149}
150
151fn extract_modality(flags: u64) -> u8 {
153 ((flags >> 6) & 0x7) as u8
154}
155
156#[allow(clippy::match_same_arms)] fn extract_class_kind(flags: u64) -> KotlinClassKind {
159 match (flags >> 9) & 0x7 {
160 0 => KotlinClassKind::Class,
161 1 => KotlinClassKind::Interface,
162 2 => KotlinClassKind::EnumClass,
163 3 => KotlinClassKind::EnumEntry,
164 4 => KotlinClassKind::AnnotationClass,
165 5 => KotlinClassKind::Object,
166 6 => KotlinClassKind::CompanionObject,
167 _ => KotlinClassKind::Class, }
169}
170
171struct WireReader<'a> {
180 data: &'a [u8],
181 pos: usize,
182}
183
184impl<'a> WireReader<'a> {
185 fn new(data: &'a [u8]) -> Self {
187 Self { data, pos: 0 }
188 }
189
190 fn has_remaining(&self) -> bool {
192 self.pos < self.data.len()
193 }
194
195 fn read_varint(&mut self) -> Option<u64> {
200 let mut result: u64 = 0;
201 let mut shift: u32 = 0;
202
203 for _ in 0..10 {
204 if self.pos >= self.data.len() {
205 return None;
206 }
207 let byte = self.data[self.pos];
208 self.pos += 1;
209
210 result |= u64::from(byte & 0x7F) << shift;
211 if byte & 0x80 == 0 {
212 return Some(result);
213 }
214 shift += 7;
215 }
216
217 None
219 }
220
221 fn read_tag(&mut self) -> Option<(u32, u8)> {
225 let raw = self.read_varint()?;
226 let wire_type = (raw & 0x7) as u8;
227 let field_number = (raw >> 3) as u32;
228 if field_number == 0 {
229 return None; }
231 Some((field_number, wire_type))
232 }
233
234 fn read_length_delimited(&mut self) -> Option<&'a [u8]> {
238 let len = self.read_varint()? as usize;
239 if self.pos + len > self.data.len() {
240 return None;
241 }
242 let slice = &self.data[self.pos..self.pos + len];
243 self.pos += len;
244 Some(slice)
245 }
246
247 fn skip_field(&mut self, wire_type: u8) -> bool {
251 match wire_type {
252 WIRE_VARINT => self.read_varint().is_some(),
253 WIRE_64BIT => {
254 if self.pos + 8 > self.data.len() {
255 return false;
256 }
257 self.pos += 8;
258 true
259 }
260 WIRE_LENGTH_DELIMITED => self.read_length_delimited().is_some(),
261 WIRE_32BIT => {
262 if self.pos + 4 > self.data.len() {
263 return false;
264 }
265 self.pos += 4;
266 true
267 }
268 _ => false, }
270 }
271}
272
273fn string_table_lookup(d2: &[String], index: u64) -> Option<&str> {
281 let idx = index as usize;
282 d2.get(idx).map(String::as_str)
283}
284
285struct DecodedType {
291 nullable: bool,
293 class_name_index: Option<u64>,
295}
296
297fn decode_type(data: &[u8]) -> Option<DecodedType> {
302 let mut reader = WireReader::new(data);
303 let mut flags: u64 = 0;
304 let mut class_name_index: Option<u64> = None;
305
306 while reader.has_remaining() {
307 let (field_number, wire_type) = reader.read_tag()?;
308
309 match (field_number, wire_type) {
310 (TYPE_FLAGS, WIRE_VARINT) => {
311 flags = reader.read_varint()?;
312 }
313 (TYPE_CLASS_NAME, WIRE_VARINT) => {
314 class_name_index = Some(reader.read_varint()?);
315 }
316 _ => {
317 if !reader.skip_field(wire_type) {
318 return None;
319 }
320 }
321 }
322 }
323
324 Some(DecodedType {
325 nullable: (flags >> 1) & 1 == 1,
326 class_name_index,
327 })
328}
329
330struct DecodedFunction {
336 name_index: u64,
338 receiver_type: Option<DecodedType>,
340}
341
342fn decode_function(data: &[u8]) -> Option<DecodedFunction> {
347 let mut reader = WireReader::new(data);
348 let mut name_index: u64 = 0;
349 let mut receiver_type: Option<DecodedType> = None;
350
351 while reader.has_remaining() {
352 let (field_number, wire_type) = reader.read_tag()?;
353
354 match (field_number, wire_type) {
355 (FUNCTION_FLAGS, WIRE_VARINT) => {
356 let _flags = reader.read_varint()?;
358 }
359 (FUNCTION_NAME, WIRE_VARINT) => {
360 name_index = reader.read_varint()?;
361 }
362 (FUNCTION_RECEIVER_TYPE, WIRE_LENGTH_DELIMITED) => {
363 let type_data = reader.read_length_delimited()?;
364 receiver_type = decode_type(type_data);
365 }
366 _ => {
367 if !reader.skip_field(wire_type) {
368 return None;
369 }
370 }
371 }
372 }
373
374 Some(DecodedFunction {
375 name_index,
376 receiver_type,
377 })
378}
379
380struct DecodedProperty {
386 name_index: u64,
388 return_type_nullable: bool,
390}
391
392fn decode_property(data: &[u8]) -> Option<DecodedProperty> {
396 let mut reader = WireReader::new(data);
397 let mut name_index: u64 = 0;
398 let mut return_type_nullable = false;
399
400 while reader.has_remaining() {
401 let (field_number, wire_type) = reader.read_tag()?;
402
403 match (field_number, wire_type) {
404 (PROPERTY_FLAGS, WIRE_VARINT) => {
405 let _flags = reader.read_varint()?;
406 }
407 (PROPERTY_NAME, WIRE_VARINT) => {
408 name_index = reader.read_varint()?;
409 }
410 (PROPERTY_RETURN_TYPE, WIRE_LENGTH_DELIMITED) => {
411 let type_data = reader.read_length_delimited()?;
412 if let Some(decoded) = decode_type(type_data) {
413 return_type_nullable = decoded.nullable;
414 }
415 }
416 _ => {
417 if !reader.skip_field(wire_type) {
418 return None;
419 }
420 }
421 }
422 }
423
424 Some(DecodedProperty {
425 name_index,
426 return_type_nullable,
427 })
428}
429
430fn decode_class_message(data: &[u8], string_table: &[String]) -> Option<KotlinClassMetadata> {
439 let mut reader = WireReader::new(data);
440 let mut flags: u64 = 0;
441 let mut companion_name_index: Option<u64> = None;
442 let mut extension_functions = Vec::new();
443 let mut nullable_properties = Vec::new();
444
445 while reader.has_remaining() {
446 let (field_number, wire_type) = reader.read_tag()?;
447
448 match (field_number, wire_type) {
449 (CLASS_FLAGS, WIRE_VARINT) => {
450 flags = reader.read_varint()?;
451 }
452 (CLASS_FUNCTIONS, WIRE_LENGTH_DELIMITED) => {
453 let func_data = reader.read_length_delimited()?;
454 if let Some(func) = decode_function(func_data)
455 && let Some(ref recv_type) = func.receiver_type
456 {
457 let fn_name = string_table_lookup(string_table, func.name_index)
459 .unwrap_or("<unknown>")
460 .to_owned();
461
462 let receiver_name = recv_type
463 .class_name_index
464 .and_then(|idx| string_table_lookup(string_table, idx))
465 .unwrap_or("<unknown>")
466 .to_owned();
467
468 extension_functions.push(KotlinExtensionFunction {
469 name: fn_name,
470 receiver_type: receiver_name,
471 receiver_nullable: recv_type.nullable,
472 });
473 }
474 }
475 (CLASS_PROPERTIES, WIRE_LENGTH_DELIMITED) => {
476 let prop_data = reader.read_length_delimited()?;
477 if let Some(prop) = decode_property(prop_data)
478 && prop.return_type_nullable
479 && let Some(name) = string_table_lookup(string_table, prop.name_index)
480 {
481 nullable_properties.push(name.to_owned());
482 }
483 }
484 (CLASS_COMPANION_OBJECT_NAME, WIRE_VARINT) => {
485 companion_name_index = Some(reader.read_varint()?);
486 }
487 _ => {
488 if !reader.skip_field(wire_type) {
489 return None;
490 }
491 }
492 }
493 }
494
495 let class_kind = extract_class_kind(flags);
496 let visibility = extract_visibility(flags);
497 let modality = extract_modality(flags);
498
499 let is_data = class_kind == KotlinClassKind::Class && (flags >> 12) & 1 == 1;
502 let is_sealed = modality == 3; let companion_object_name = companion_name_index
505 .and_then(|idx| string_table_lookup(string_table, idx))
506 .map(str::to_owned);
507
508 Some(KotlinClassMetadata {
509 kind: class_kind,
510 visibility,
511 is_data,
512 is_sealed,
513 companion_object_name,
514 extension_functions,
515 nullable_properties,
516 })
517}
518
519#[must_use]
534pub fn decode_kotlin_metadata(stub: &KotlinMetadataStub) -> Option<KotlinClassMetadata> {
535 if stub.kind != 1 {
537 log::debug!(
538 "skipping Kotlin metadata kind {} (only kind=1 Class is supported)",
539 stub.kind,
540 );
541 return None;
542 }
543
544 if let Some(&major) = stub.metadata_version.first()
546 && !(1..=2).contains(&major)
547 {
548 log::warn!(
549 "unsupported Kotlin metadata version {:?}, skipping",
550 stub.metadata_version,
551 );
552 return None;
553 }
554
555 let d1_combined = combine_d1_chunks(&stub.data1)?;
557
558 decode_class_message(&d1_combined, &stub.data2)
560}
561
562fn combine_d1_chunks(d1: &[String]) -> Option<Vec<u8>> {
572 if d1.is_empty() {
573 return None;
574 }
575
576 let total_chars: usize = d1.iter().map(|s| s.chars().count()).sum();
577 let mut bytes = Vec::with_capacity(total_chars);
578 for chunk in d1 {
579 for ch in chunk.chars() {
580 bytes.push((ch as u32 & 0xFF) as u8);
584 }
585 }
586 Some(bytes)
587}
588
589#[cfg(test)]
594mod tests {
595 use super::*;
596
597 fn encode_varint(mut value: u64) -> Vec<u8> {
601 let mut buf = Vec::new();
602 loop {
603 let mut byte = (value & 0x7F) as u8;
604 value >>= 7;
605 if value != 0 {
606 byte |= 0x80;
607 }
608 buf.push(byte);
609 if value == 0 {
610 break;
611 }
612 }
613 buf
614 }
615
616 fn encode_tag(field_number: u32, wire_type: u8) -> Vec<u8> {
618 encode_varint(u64::from(field_number) << 3 | u64::from(wire_type))
619 }
620
621 fn encode_varint_field(field_number: u32, value: u64) -> Vec<u8> {
623 let mut buf = encode_tag(field_number, WIRE_VARINT);
624 buf.extend(encode_varint(value));
625 buf
626 }
627
628 fn encode_length_delimited_field(field_number: u32, data: &[u8]) -> Vec<u8> {
630 let mut buf = encode_tag(field_number, WIRE_LENGTH_DELIMITED);
631 buf.extend(encode_varint(data.len() as u64));
632 buf.extend(data);
633 buf
634 }
635
636 fn build_class_flags(visibility: u64, modality: u64, class_kind: u64, is_data: bool) -> u64 {
638 let mut flags = 0u64;
639 flags |= visibility << 3;
640 flags |= modality << 6;
641 flags |= class_kind << 9;
642 if is_data {
643 flags |= 1 << 12;
644 }
645 flags
646 }
647
648 fn build_type_message(nullable: bool, class_name_index: Option<u64>) -> Vec<u8> {
650 let mut buf = Vec::new();
651 let mut flags: u64 = 0;
652 if nullable {
653 flags |= 1 << 1;
654 }
655 if flags != 0 {
656 buf.extend(encode_varint_field(TYPE_FLAGS, flags));
657 }
658 if let Some(idx) = class_name_index {
659 buf.extend(encode_varint_field(TYPE_CLASS_NAME, idx));
660 }
661 buf
662 }
663
664 fn build_function_message(name_index: u64, receiver_type: Option<&[u8]>) -> Vec<u8> {
666 let mut buf = Vec::new();
667 buf.extend(encode_varint_field(FUNCTION_FLAGS, 0));
669 buf.extend(encode_varint_field(FUNCTION_NAME, name_index));
671 if let Some(rt) = receiver_type {
673 buf.extend(encode_length_delimited_field(FUNCTION_RECEIVER_TYPE, rt));
674 }
675 buf
676 }
677
678 fn build_property_message(name_index: u64, return_type: Option<&[u8]>) -> Vec<u8> {
680 let mut buf = Vec::new();
681 buf.extend(encode_varint_field(PROPERTY_FLAGS, 0));
682 buf.extend(encode_varint_field(PROPERTY_NAME, name_index));
683 if let Some(rt) = return_type {
684 buf.extend(encode_length_delimited_field(PROPERTY_RETURN_TYPE, rt));
685 }
686 buf
687 }
688
689 #[allow(clippy::needless_continue)] #[allow(clippy::needless_pass_by_value)] fn make_stub(d1_bytes: Vec<u8>, string_table: Vec<&str>) -> KotlinMetadataStub {
696 let d1_string: String = d1_bytes.iter().map(|&b| b as char).collect();
697 KotlinMetadataStub {
698 kind: 1,
699 metadata_version: vec![1, 9, 0],
700 data1: vec![d1_string],
701 data2: string_table.into_iter().map(str::to_owned).collect(),
702 extra_string: None,
703 package_name: None,
704 extra_int: None,
705 }
706 }
707
708 #[test]
711 fn wire_reader_varint_single_byte() {
712 let data = [0x05]; let mut reader = WireReader::new(&data);
714 assert_eq!(reader.read_varint(), Some(5));
715 assert!(!reader.has_remaining());
716 }
717
718 #[test]
719 fn wire_reader_varint_multi_byte() {
720 let data = [0xAC, 0x02];
724 let mut reader = WireReader::new(&data);
725 assert_eq!(reader.read_varint(), Some(300));
726 }
727
728 #[test]
729 fn wire_reader_varint_max_bytes() {
730 let encoded = encode_varint(u64::MAX);
732 assert_eq!(encoded.len(), 10);
733 let mut reader = WireReader::new(&encoded);
734 assert_eq!(reader.read_varint(), Some(u64::MAX));
735 }
736
737 #[test]
738 fn wire_reader_varint_truncated() {
739 let data = [0x80];
741 let mut reader = WireReader::new(&data);
742 assert_eq!(reader.read_varint(), None);
743 }
744
745 #[test]
746 fn wire_reader_varint_exceeds_10_bytes() {
747 let data = [0x80; 11];
749 let mut reader = WireReader::new(&data);
750 assert_eq!(reader.read_varint(), None);
751 }
752
753 #[test]
754 fn wire_reader_tag_decomposition() {
755 let data = [0x1A];
757 let mut reader = WireReader::new(&data);
758 assert_eq!(reader.read_tag(), Some((3, 2)));
759 }
760
761 #[test]
762 fn wire_reader_tag_field_zero_invalid() {
763 let data = [0x02]; let mut reader = WireReader::new(&data);
766 assert_eq!(reader.read_tag(), None);
767 }
768
769 #[test]
770 fn wire_reader_length_delimited() {
771 let data = [0x0A, 0x03, 0x01, 0x02, 0x03];
774 let mut reader = WireReader::new(&data);
775 let (field, wire) = reader.read_tag().unwrap();
776 assert_eq!((field, wire), (1, 2));
777 let payload = reader.read_length_delimited().unwrap();
778 assert_eq!(payload, &[0x01, 0x02, 0x03]);
779 }
780
781 #[test]
782 fn wire_reader_length_delimited_truncated() {
783 let data = [0x05, 0x01, 0x02];
785 let mut reader = WireReader::new(&data);
786 assert_eq!(reader.read_length_delimited(), None);
787 }
788
789 #[test]
790 fn wire_reader_skip_varint() {
791 let mut data = encode_tag(99, WIRE_VARINT);
792 data.extend(encode_varint(42));
793 data.extend(encode_tag(1, WIRE_VARINT));
794 data.extend(encode_varint(7));
795
796 let mut reader = WireReader::new(&data);
797 let (field, wire) = reader.read_tag().unwrap();
798 assert_eq!(field, 99);
799 assert!(reader.skip_field(wire));
800
801 let (field2, _) = reader.read_tag().unwrap();
802 assert_eq!(field2, 1);
803 }
804
805 #[test]
806 fn wire_reader_skip_32bit() {
807 let mut data = vec![];
808 data.extend(encode_tag(5, WIRE_32BIT));
809 data.extend(&[0x00, 0x00, 0x00, 0x00]); data.extend(encode_tag(1, WIRE_VARINT));
811 data.extend(encode_varint(99));
812
813 let mut reader = WireReader::new(&data);
814 let (_, wire) = reader.read_tag().unwrap();
815 assert!(reader.skip_field(wire));
816 let (field, _) = reader.read_tag().unwrap();
817 assert_eq!(field, 1);
818 }
819
820 #[test]
821 fn wire_reader_skip_64bit() {
822 let mut data = vec![];
823 data.extend(encode_tag(5, WIRE_64BIT));
824 data.extend(&[0u8; 8]); data.extend(encode_tag(1, WIRE_VARINT));
826 data.extend(encode_varint(99));
827
828 let mut reader = WireReader::new(&data);
829 let (_, wire) = reader.read_tag().unwrap();
830 assert!(reader.skip_field(wire));
831 let (field, _) = reader.read_tag().unwrap();
832 assert_eq!(field, 1);
833 }
834
835 #[test]
836 fn wire_reader_skip_unknown_wire_type() {
837 let mut reader = WireReader::new(&[]);
838 assert!(!reader.skip_field(3)); }
840
841 #[test]
844 fn string_table_valid_lookup() {
845 let table = vec!["kotlin/String".to_owned(), "isEmail".to_owned()];
846 assert_eq!(string_table_lookup(&table, 0), Some("kotlin/String"));
847 assert_eq!(string_table_lookup(&table, 1), Some("isEmail"));
848 }
849
850 #[test]
851 fn string_table_out_of_bounds() {
852 let table = vec!["only_one".to_owned()];
853 assert_eq!(string_table_lookup(&table, 1), None);
854 assert_eq!(string_table_lookup(&table, 999), None);
855 }
856
857 #[test]
860 fn extension_receiver_detection() {
861 let receiver_type = build_type_message(false, Some(0));
863 let func = build_function_message(1, Some(&receiver_type));
864
865 let mut d1 = Vec::new();
866 d1.extend(encode_varint_field(
868 CLASS_FLAGS,
869 build_class_flags(3, 0, 0, false),
870 ));
871 d1.extend(encode_length_delimited_field(CLASS_FUNCTIONS, &func));
873
874 let stub = make_stub(d1, vec!["kotlin/String", "isEmail"]);
875 let meta = decode_kotlin_metadata(&stub).unwrap();
876
877 assert_eq!(meta.extension_functions.len(), 1);
878 assert_eq!(meta.extension_functions[0].name, "isEmail");
879 assert_eq!(meta.extension_functions[0].receiver_type, "kotlin/String");
880 assert!(!meta.extension_functions[0].receiver_nullable);
881 }
882
883 #[test]
884 fn extension_receiver_nullable() {
885 let receiver_type = build_type_message(true, Some(0));
887 let func = build_function_message(1, Some(&receiver_type));
888
889 let mut d1 = Vec::new();
890 d1.extend(encode_varint_field(
891 CLASS_FLAGS,
892 build_class_flags(3, 0, 0, false),
893 ));
894 d1.extend(encode_length_delimited_field(CLASS_FUNCTIONS, &func));
895
896 let stub = make_stub(d1, vec!["kotlin/String", "isNullOrEmail"]);
897 let meta = decode_kotlin_metadata(&stub).unwrap();
898
899 assert_eq!(meta.extension_functions.len(), 1);
900 assert_eq!(meta.extension_functions[0].name, "isNullOrEmail");
901 assert!(meta.extension_functions[0].receiver_nullable);
902 }
903
904 #[test]
905 fn regular_function_not_treated_as_extension() {
906 let func = build_function_message(0, None);
908
909 let mut d1 = Vec::new();
910 d1.extend(encode_varint_field(
911 CLASS_FLAGS,
912 build_class_flags(3, 0, 0, false),
913 ));
914 d1.extend(encode_length_delimited_field(CLASS_FUNCTIONS, &func));
915
916 let stub = make_stub(d1, vec!["regularFunction"]);
917 let meta = decode_kotlin_metadata(&stub).unwrap();
918
919 assert!(meta.extension_functions.is_empty());
920 }
921
922 #[test]
925 fn nullable_property_detection() {
926 let nullable_type = build_type_message(true, Some(0));
928 let prop = build_property_message(1, Some(&nullable_type));
929
930 let mut d1 = Vec::new();
931 d1.extend(encode_varint_field(
932 CLASS_FLAGS,
933 build_class_flags(3, 0, 0, false),
934 ));
935 d1.extend(encode_length_delimited_field(CLASS_PROPERTIES, &prop));
936
937 let stub = make_stub(d1, vec!["kotlin/String", "name"]);
938 let meta = decode_kotlin_metadata(&stub).unwrap();
939
940 assert_eq!(meta.nullable_properties, vec!["name"]);
941 }
942
943 #[test]
944 fn non_nullable_property_not_included() {
945 let non_nullable_type = build_type_message(false, Some(0));
946 let prop = build_property_message(1, Some(&non_nullable_type));
947
948 let mut d1 = Vec::new();
949 d1.extend(encode_varint_field(
950 CLASS_FLAGS,
951 build_class_flags(3, 0, 0, false),
952 ));
953 d1.extend(encode_length_delimited_field(CLASS_PROPERTIES, &prop));
954
955 let stub = make_stub(d1, vec!["kotlin/String", "name"]);
956 let meta = decode_kotlin_metadata(&stub).unwrap();
957
958 assert!(meta.nullable_properties.is_empty());
959 }
960
961 #[test]
964 fn companion_object_detection() {
965 let mut d1 = Vec::new();
966 d1.extend(encode_varint_field(
967 CLASS_FLAGS,
968 build_class_flags(3, 0, 0, false),
969 ));
970 d1.extend(encode_varint_field(CLASS_COMPANION_OBJECT_NAME, 0));
972
973 let stub = make_stub(d1, vec!["Companion"]);
974 let meta = decode_kotlin_metadata(&stub).unwrap();
975
976 assert_eq!(meta.companion_object_name, Some("Companion".to_owned()));
977 }
978
979 #[test]
980 fn companion_object_custom_name() {
981 let mut d1 = Vec::new();
982 d1.extend(encode_varint_field(
983 CLASS_FLAGS,
984 build_class_flags(3, 0, 0, false),
985 ));
986 d1.extend(encode_varint_field(CLASS_COMPANION_OBJECT_NAME, 0));
987
988 let stub = make_stub(d1, vec!["Factory"]);
989 let meta = decode_kotlin_metadata(&stub).unwrap();
990
991 assert_eq!(meta.companion_object_name, Some("Factory".to_owned()));
992 }
993
994 #[test]
995 fn no_companion_object() {
996 let mut d1 = Vec::new();
997 d1.extend(encode_varint_field(
998 CLASS_FLAGS,
999 build_class_flags(3, 0, 0, false),
1000 ));
1001
1002 let stub = make_stub(d1, vec![]);
1003 let meta = decode_kotlin_metadata(&stub).unwrap();
1004
1005 assert_eq!(meta.companion_object_name, None);
1006 }
1007
1008 #[test]
1011 fn object_declaration_kind() {
1012 let mut d1 = Vec::new();
1013 d1.extend(encode_varint_field(
1015 CLASS_FLAGS,
1016 build_class_flags(3, 0, 5, false),
1017 ));
1018
1019 let stub = make_stub(d1, vec![]);
1020 let meta = decode_kotlin_metadata(&stub).unwrap();
1021
1022 assert_eq!(meta.kind, KotlinClassKind::Object);
1023 }
1024
1025 #[test]
1026 fn companion_object_kind() {
1027 let mut d1 = Vec::new();
1028 d1.extend(encode_varint_field(
1030 CLASS_FLAGS,
1031 build_class_flags(3, 0, 6, false),
1032 ));
1033
1034 let stub = make_stub(d1, vec![]);
1035 let meta = decode_kotlin_metadata(&stub).unwrap();
1036
1037 assert_eq!(meta.kind, KotlinClassKind::CompanionObject);
1038 }
1039
1040 #[test]
1043 fn data_class_detection() {
1044 let mut d1 = Vec::new();
1045 d1.extend(encode_varint_field(
1047 CLASS_FLAGS,
1048 build_class_flags(3, 0, 0, true),
1049 ));
1050
1051 let stub = make_stub(d1, vec![]);
1052 let meta = decode_kotlin_metadata(&stub).unwrap();
1053
1054 assert!(meta.is_data);
1055 assert_eq!(meta.kind, KotlinClassKind::Class);
1056 }
1057
1058 #[test]
1059 fn non_data_class() {
1060 let mut d1 = Vec::new();
1061 d1.extend(encode_varint_field(
1062 CLASS_FLAGS,
1063 build_class_flags(3, 0, 0, false),
1064 ));
1065
1066 let stub = make_stub(d1, vec![]);
1067 let meta = decode_kotlin_metadata(&stub).unwrap();
1068
1069 assert!(!meta.is_data);
1070 }
1071
1072 #[test]
1075 fn sealed_class_detection() {
1076 let mut d1 = Vec::new();
1077 d1.extend(encode_varint_field(
1079 CLASS_FLAGS,
1080 build_class_flags(3, 3, 0, false),
1081 ));
1082
1083 let stub = make_stub(d1, vec![]);
1084 let meta = decode_kotlin_metadata(&stub).unwrap();
1085
1086 assert!(meta.is_sealed);
1087 }
1088
1089 #[test]
1090 fn non_sealed_class() {
1091 let mut d1 = Vec::new();
1092 d1.extend(encode_varint_field(
1094 CLASS_FLAGS,
1095 build_class_flags(3, 0, 0, false),
1096 ));
1097
1098 let stub = make_stub(d1, vec![]);
1099 let meta = decode_kotlin_metadata(&stub).unwrap();
1100
1101 assert!(!meta.is_sealed);
1102 }
1103
1104 #[test]
1107 fn visibility_public() {
1108 let mut d1 = Vec::new();
1109 d1.extend(encode_varint_field(
1110 CLASS_FLAGS,
1111 build_class_flags(3, 0, 0, false), ));
1113
1114 let stub = make_stub(d1, vec![]);
1115 let meta = decode_kotlin_metadata(&stub).unwrap();
1116 assert_eq!(meta.visibility, KotlinVisibility::Public);
1117 }
1118
1119 #[test]
1120 fn visibility_private() {
1121 let mut d1 = Vec::new();
1122 d1.extend(encode_varint_field(
1123 CLASS_FLAGS,
1124 build_class_flags(1, 0, 0, false), ));
1126
1127 let stub = make_stub(d1, vec![]);
1128 let meta = decode_kotlin_metadata(&stub).unwrap();
1129 assert_eq!(meta.visibility, KotlinVisibility::Private);
1130 }
1131
1132 #[test]
1133 fn visibility_internal() {
1134 let mut d1 = Vec::new();
1135 d1.extend(encode_varint_field(
1136 CLASS_FLAGS,
1137 build_class_flags(0, 0, 0, false), ));
1139
1140 let stub = make_stub(d1, vec![]);
1141 let meta = decode_kotlin_metadata(&stub).unwrap();
1142 assert_eq!(meta.visibility, KotlinVisibility::Internal);
1143 }
1144
1145 #[test]
1146 fn visibility_protected() {
1147 let mut d1 = Vec::new();
1148 d1.extend(encode_varint_field(
1149 CLASS_FLAGS,
1150 build_class_flags(2, 0, 0, false), ));
1152
1153 let stub = make_stub(d1, vec![]);
1154 let meta = decode_kotlin_metadata(&stub).unwrap();
1155 assert_eq!(meta.visibility, KotlinVisibility::Protected);
1156 }
1157
1158 #[test]
1161 fn malformed_protobuf_returns_none() {
1162 let stub = make_stub(
1165 vec![
1166 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
1167 ],
1168 vec![],
1169 );
1170 let _result = decode_kotlin_metadata(&stub);
1173 }
1174
1175 #[test]
1176 fn empty_d1_returns_none() {
1177 let stub = KotlinMetadataStub {
1178 kind: 1,
1179 metadata_version: vec![1, 9, 0],
1180 data1: vec![],
1181 data2: vec![],
1182 extra_string: None,
1183 package_name: None,
1184 extra_int: None,
1185 };
1186 assert_eq!(decode_kotlin_metadata(&stub), None);
1187 }
1188
1189 #[test]
1190 fn truncated_varint_returns_none() {
1191 let stub = make_stub(vec![0x80], vec![]);
1193 assert_eq!(decode_kotlin_metadata(&stub), None);
1194 }
1195
1196 #[test]
1199 fn unsupported_kind_returns_none() {
1200 let stub = KotlinMetadataStub {
1201 kind: 2, metadata_version: vec![1, 9, 0],
1203 data1: vec!["data".to_owned()],
1204 data2: vec![],
1205 extra_string: None,
1206 package_name: None,
1207 extra_int: None,
1208 };
1209 assert_eq!(decode_kotlin_metadata(&stub), None);
1210 }
1211
1212 #[test]
1213 fn unsupported_kind_synthetic() {
1214 let stub = KotlinMetadataStub {
1215 kind: 3,
1216 metadata_version: vec![1, 9, 0],
1217 data1: vec![],
1218 data2: vec![],
1219 extra_string: None,
1220 package_name: None,
1221 extra_int: None,
1222 };
1223 assert_eq!(decode_kotlin_metadata(&stub), None);
1224 }
1225
1226 #[test]
1227 fn unsupported_metadata_version() {
1228 let stub = KotlinMetadataStub {
1229 kind: 1,
1230 metadata_version: vec![99, 0, 0], data1: vec!["data".to_owned()],
1232 data2: vec![],
1233 extra_string: None,
1234 package_name: None,
1235 extra_int: None,
1236 };
1237 assert_eq!(decode_kotlin_metadata(&stub), None);
1238 }
1239
1240 #[test]
1243 fn comprehensive_class_decoding() {
1244 let string_table = vec!["kotlin/String", "isEmail", "name", "Companion", "toString"];
1251
1252 let receiver_type = build_type_message(false, Some(0));
1254 let ext_func = build_function_message(1, Some(&receiver_type));
1255
1256 let regular_func = build_function_message(4, None);
1258
1259 let nullable_type = build_type_message(true, Some(0));
1261 let nullable_prop = build_property_message(2, Some(&nullable_type));
1262
1263 let mut d1 = Vec::new();
1265 d1.extend(encode_varint_field(
1267 CLASS_FLAGS,
1268 build_class_flags(3, 0, 0, false),
1269 ));
1270 d1.extend(encode_length_delimited_field(CLASS_FUNCTIONS, &ext_func));
1271 d1.extend(encode_length_delimited_field(
1272 CLASS_FUNCTIONS,
1273 ®ular_func,
1274 ));
1275 d1.extend(encode_length_delimited_field(
1276 CLASS_PROPERTIES,
1277 &nullable_prop,
1278 ));
1279 d1.extend(encode_varint_field(CLASS_COMPANION_OBJECT_NAME, 3));
1280
1281 let stub = make_stub(d1, string_table);
1282 let meta = decode_kotlin_metadata(&stub).unwrap();
1283
1284 assert_eq!(meta.kind, KotlinClassKind::Class);
1285 assert_eq!(meta.visibility, KotlinVisibility::Public);
1286 assert!(!meta.is_data);
1287 assert!(!meta.is_sealed);
1288 assert_eq!(meta.companion_object_name, Some("Companion".to_owned()));
1289 assert_eq!(meta.extension_functions.len(), 1);
1290 assert_eq!(meta.extension_functions[0].name, "isEmail");
1291 assert_eq!(meta.extension_functions[0].receiver_type, "kotlin/String");
1292 assert_eq!(meta.nullable_properties, vec!["name"]);
1293 }
1294
1295 #[test]
1298 fn interface_kind_detection() {
1299 let mut d1 = Vec::new();
1300 d1.extend(encode_varint_field(
1302 CLASS_FLAGS,
1303 build_class_flags(3, 2, 1, false), ));
1305
1306 let stub = make_stub(d1, vec![]);
1307 let meta = decode_kotlin_metadata(&stub).unwrap();
1308
1309 assert_eq!(meta.kind, KotlinClassKind::Interface);
1310 }
1311
1312 #[test]
1315 fn enum_class_kind_detection() {
1316 let mut d1 = Vec::new();
1317 d1.extend(encode_varint_field(
1319 CLASS_FLAGS,
1320 build_class_flags(3, 0, 2, false),
1321 ));
1322
1323 let stub = make_stub(d1, vec![]);
1324 let meta = decode_kotlin_metadata(&stub).unwrap();
1325
1326 assert_eq!(meta.kind, KotlinClassKind::EnumClass);
1327 }
1328
1329 #[test]
1332 fn annotation_class_kind_detection() {
1333 let mut d1 = Vec::new();
1334 d1.extend(encode_varint_field(
1336 CLASS_FLAGS,
1337 build_class_flags(3, 0, 4, false),
1338 ));
1339
1340 let stub = make_stub(d1, vec![]);
1341 let meta = decode_kotlin_metadata(&stub).unwrap();
1342
1343 assert_eq!(meta.kind, KotlinClassKind::AnnotationClass);
1344 }
1345
1346 #[test]
1349 fn multiple_extension_functions() {
1350 let recv_string = build_type_message(false, Some(0));
1351 let recv_list = build_type_message(false, Some(2));
1352
1353 let func1 = build_function_message(1, Some(&recv_string));
1354 let func2 = build_function_message(3, Some(&recv_list));
1355
1356 let mut d1 = Vec::new();
1357 d1.extend(encode_varint_field(
1358 CLASS_FLAGS,
1359 build_class_flags(3, 0, 0, false),
1360 ));
1361 d1.extend(encode_length_delimited_field(CLASS_FUNCTIONS, &func1));
1362 d1.extend(encode_length_delimited_field(CLASS_FUNCTIONS, &func2));
1363
1364 let stub = make_stub(
1365 d1,
1366 vec!["kotlin/String", "isEmail", "kotlin/List", "firstOrNull"],
1367 );
1368 let meta = decode_kotlin_metadata(&stub).unwrap();
1369
1370 assert_eq!(meta.extension_functions.len(), 2);
1371 assert_eq!(meta.extension_functions[0].name, "isEmail");
1372 assert_eq!(meta.extension_functions[0].receiver_type, "kotlin/String");
1373 assert_eq!(meta.extension_functions[1].name, "firstOrNull");
1374 assert_eq!(meta.extension_functions[1].receiver_type, "kotlin/List");
1375 }
1376
1377 #[test]
1380 fn multiple_nullable_properties() {
1381 let nullable_type = build_type_message(true, Some(0));
1382 let prop1 = build_property_message(1, Some(&nullable_type));
1383 let prop2 = build_property_message(2, Some(&nullable_type));
1384
1385 let mut d1 = Vec::new();
1386 d1.extend(encode_varint_field(
1387 CLASS_FLAGS,
1388 build_class_flags(3, 0, 0, false),
1389 ));
1390 d1.extend(encode_length_delimited_field(CLASS_PROPERTIES, &prop1));
1391 d1.extend(encode_length_delimited_field(CLASS_PROPERTIES, &prop2));
1392
1393 let stub = make_stub(d1, vec!["kotlin/String", "name", "email"]);
1394 let meta = decode_kotlin_metadata(&stub).unwrap();
1395
1396 assert_eq!(meta.nullable_properties.len(), 2);
1397 assert!(meta.nullable_properties.contains(&"name".to_owned()));
1398 assert!(meta.nullable_properties.contains(&"email".to_owned()));
1399 }
1400
1401 #[test]
1404 fn combine_d1_multiple_chunks() {
1405 let d1 = vec!["hel".to_owned(), "lo".to_owned()];
1406 let combined = combine_d1_chunks(&d1).unwrap();
1407 assert_eq!(combined, b"hello");
1408 }
1409
1410 #[test]
1411 fn combine_d1_empty() {
1412 let d1: Vec<String> = vec![];
1413 assert_eq!(combine_d1_chunks(&d1), None);
1414 }
1415
1416 #[test]
1419 fn data_class_with_sealed_is_not_data() {
1420 let mut d1 = Vec::new();
1422 d1.extend(encode_varint_field(
1424 CLASS_FLAGS,
1425 build_class_flags(3, 3, 0, true),
1426 ));
1427
1428 let stub = make_stub(d1, vec![]);
1429 let meta = decode_kotlin_metadata(&stub).unwrap();
1430
1431 assert!(meta.is_data);
1432 assert!(meta.is_sealed);
1433 }
1434
1435 #[test]
1438 fn missing_flags_defaults_to_internal_final_class() {
1439 let d1 = Vec::new();
1441 let stub = make_stub(d1, vec![]);
1442 let meta = decode_kotlin_metadata(&stub).unwrap();
1443
1444 assert_eq!(meta.kind, KotlinClassKind::Class);
1446 assert_eq!(meta.visibility, KotlinVisibility::Internal);
1447 assert!(!meta.is_data);
1448 assert!(!meta.is_sealed);
1449 assert!(meta.companion_object_name.is_none());
1450 assert!(meta.extension_functions.is_empty());
1451 assert!(meta.nullable_properties.is_empty());
1452 }
1453
1454 #[test]
1457 fn visibility_private_to_this() {
1458 let mut d1 = Vec::new();
1459 d1.extend(encode_varint_field(
1460 CLASS_FLAGS,
1461 build_class_flags(4, 0, 0, false),
1462 ));
1463
1464 let stub = make_stub(d1, vec![]);
1465 let meta = decode_kotlin_metadata(&stub).unwrap();
1466 assert_eq!(meta.visibility, KotlinVisibility::PrivateToThis);
1467 }
1468
1469 #[test]
1470 fn visibility_local() {
1471 let mut d1 = Vec::new();
1472 d1.extend(encode_varint_field(
1473 CLASS_FLAGS,
1474 build_class_flags(5, 0, 0, false),
1475 ));
1476
1477 let stub = make_stub(d1, vec![]);
1478 let meta = decode_kotlin_metadata(&stub).unwrap();
1479 assert_eq!(meta.visibility, KotlinVisibility::Local);
1480 }
1481
1482 #[test]
1485 fn decode_type_nullable() {
1486 let data = build_type_message(true, Some(5));
1487 let decoded = decode_type(&data).unwrap();
1488 assert!(decoded.nullable);
1489 assert_eq!(decoded.class_name_index, Some(5));
1490 }
1491
1492 #[test]
1493 fn decode_type_non_nullable() {
1494 let data = build_type_message(false, Some(3));
1495 let decoded = decode_type(&data).unwrap();
1496 assert!(!decoded.nullable);
1497 assert_eq!(decoded.class_name_index, Some(3));
1498 }
1499
1500 #[test]
1501 fn decode_type_no_class_name() {
1502 let data = build_type_message(true, None);
1503 let decoded = decode_type(&data).unwrap();
1504 assert!(decoded.nullable);
1505 assert_eq!(decoded.class_name_index, None);
1506 }
1507
1508 #[test]
1509 fn decode_type_empty() {
1510 let decoded = decode_type(&[]).unwrap();
1511 assert!(!decoded.nullable);
1512 assert_eq!(decoded.class_name_index, None);
1513 }
1514}