1use padlock_core::arch::ArchConfig;
8use padlock_core::ir::{AccessPattern, Field, StructLayout, TypeInfo};
9use quote::ToTokens;
10use syn::{Fields, ItemEnum, ItemStruct, Type, visit::Visit};
11
12pub fn extract_guard_from_attrs(attrs: &[syn::Attribute]) -> Option<String> {
22 for attr in attrs {
23 let path = attr.path();
24 if (path.is_ident("lock_protected_by") || path.is_ident("protected_by"))
26 && let syn::Meta::NameValue(nv) = &attr.meta
27 && let syn::Expr::Lit(syn::ExprLit {
28 lit: syn::Lit::Str(s),
29 ..
30 }) = &nv.value
31 {
32 return Some(s.value());
33 }
34 if path.is_ident("guarded_by") || path.is_ident("pt_guarded_by") {
36 if let Ok(s) = attr.parse_args::<syn::LitStr>() {
38 return Some(s.value());
39 }
40 if let Ok(id) = attr.parse_args::<syn::Ident>() {
42 return Some(id.to_string());
43 }
44 }
45 }
46 None
47}
48
49fn rust_type_size_align(ty: &Type, arch: &'static ArchConfig) -> (usize, usize, TypeInfo) {
52 match ty {
53 Type::Path(tp) => {
54 let name = tp
55 .path
56 .segments
57 .last()
58 .map(|s| s.ident.to_string())
59 .unwrap_or_default();
60 let (size, align) = primitive_size_align(&name, arch);
61 (size, align, TypeInfo::Primitive { name, size, align })
62 }
63 Type::Ptr(_) | Type::Reference(_) => {
64 let s = arch.pointer_size;
65 (s, s, TypeInfo::Pointer { size: s, align: s })
66 }
67 Type::Array(arr) => {
68 let (elem_size, elem_align, elem_ty) = rust_type_size_align(&arr.elem, arch);
69 let count = array_len_from_expr(&arr.len);
70 let size = elem_size * count;
71 (
72 size,
73 elem_align,
74 TypeInfo::Array {
75 element: Box::new(elem_ty),
76 count,
77 size,
78 align: elem_align,
79 },
80 )
81 }
82 _ => {
83 let s = arch.pointer_size;
84 (
85 s,
86 s,
87 TypeInfo::Opaque {
88 name: "(unknown)".into(),
89 size: s,
90 align: s,
91 },
92 )
93 }
94 }
95}
96
97fn primitive_size_align(name: &str, arch: &'static ArchConfig) -> (usize, usize) {
98 let ps = arch.pointer_size;
99 match name {
100 "bool" | "u8" | "i8" => (1, 1),
102 "u16" | "i16" | "f16" => (2, 2),
103 "u32" | "i32" | "f32" => (4, 4),
104 "u64" | "i64" | "f64" => (8, 8),
105 "u128" | "i128" | "f128" => (16, 16),
106 "usize" | "isize" => (ps, ps),
107 "char" => (4, 4), "NonZeroU8" | "NonZeroI8" => (1, 1),
113 "NonZeroU16" | "NonZeroI16" => (2, 2),
114 "NonZeroU32" | "NonZeroI32" => (4, 4),
115 "NonZeroU64" | "NonZeroI64" => (8, 8),
116 "NonZeroU128" | "NonZeroI128" => (16, 16),
117 "NonZeroUsize" | "NonZeroIsize" => (ps, ps),
118
119 "Wrapping" | "Saturating" => (ps, ps),
124
125 "MaybeUninit" | "UnsafeCell" => (ps, ps),
129
130 "AtomicBool" | "AtomicU8" | "AtomicI8" => (1, 1),
132 "AtomicU16" | "AtomicI16" => (2, 2),
133 "AtomicU32" | "AtomicI32" => (4, 4),
134 "AtomicU64" | "AtomicI64" => (8, 8),
135 "AtomicUsize" | "AtomicIsize" | "AtomicPtr" => (ps, ps),
136
137 "Vec" | "String" | "OsString" | "CString" | "PathBuf" => (3 * ps, ps),
140 "VecDeque" | "LinkedList" | "BinaryHeap" => (3 * ps, ps),
141 "HashMap" | "HashSet" | "BTreeMap" | "BTreeSet" => (3 * ps, ps),
142
143 "Box" | "Rc" | "Arc" | "Weak" | "NonNull" | "Cell" => (ps, ps),
145
146 "RefCell" | "Mutex" | "RwLock" => (ps, ps),
150
151 "Sender" | "Receiver" | "SyncSender" => (ps, ps),
153
154 "PhantomData" | "PhantomPinned" => (0, 1),
156
157 "Duration" => (16, 8),
160 "Instant" | "SystemTime" => (16, 8),
161
162 "Pin" => (ps, ps),
164
165 "__m64" => (8, 8),
167 "__m128" | "__m128d" | "__m128i" => (16, 16),
168 "__m256" | "__m256d" | "__m256i" => (32, 32),
169 "__m512" | "__m512d" | "__m512i" => (64, 64),
170
171 "f32x4" | "i32x4" | "u32x4" => (16, 16),
173 "f64x2" | "i64x2" | "u64x2" => (16, 16),
174 "f32x8" | "i32x8" | "u32x8" => (32, 32),
175 "f64x4" | "i64x4" | "u64x4" => (32, 32),
176 "f32x16" | "i32x16" | "u32x16" => (64, 64),
177
178 _ => (ps, ps),
180 }
181}
182
183fn array_len_from_expr(expr: &syn::Expr) -> usize {
184 if let syn::Expr::Lit(syn::ExprLit {
185 lit: syn::Lit::Int(n),
186 ..
187 }) = expr
188 {
189 n.base10_parse::<usize>().unwrap_or(0)
190 } else {
191 0
192 }
193}
194
195fn is_packed(attrs: &[syn::Attribute]) -> bool {
198 attrs
199 .iter()
200 .any(|a| a.path().is_ident("repr") && a.to_token_stream().to_string().contains("packed"))
201}
202
203fn is_repr_rust(attrs: &[syn::Attribute]) -> bool {
208 !attrs.iter().any(|a| {
209 if !a.path().is_ident("repr") {
210 return false;
211 }
212 let ts = a.to_token_stream().to_string();
213 ts.contains('C') || ts.contains("packed") || ts.contains("transparent")
214 })
215}
216
217fn repr_align(attrs: &[syn::Attribute]) -> Option<usize> {
219 for attr in attrs {
220 if !attr.path().is_ident("repr") {
221 continue;
222 }
223 let ts = attr.to_token_stream().to_string();
224 if let Some(start) = ts.find("align") {
227 let after = ts[start..].trim_start_matches("align").trim_start();
228 if after.starts_with('(') {
229 let inner = after.trim_start_matches('(');
230 let num_str: String = inner.chars().take_while(|c| c.is_ascii_digit()).collect();
231 if let Ok(n) = num_str.parse::<usize>()
232 && n > 0
233 && n.is_power_of_two()
234 {
235 return Some(n);
236 }
237 }
238 }
239 }
240 None
241}
242
243fn simulate_rust_layout(
244 name: String,
245 fields: &[(String, Type)],
246 packed: bool,
247 forced_align: Option<usize>,
248 arch: &'static ArchConfig,
249) -> StructLayout {
250 let mut offset = 0usize;
251 let mut struct_align = 1usize;
252 let mut out_fields: Vec<Field> = Vec::new();
253
254 for (fname, ty) in fields {
255 let (size, align, type_info) = rust_type_size_align(ty, arch);
256 let effective_align = if packed { 1 } else { align };
257
258 if effective_align > 0 {
259 offset = offset.next_multiple_of(effective_align);
260 }
261 struct_align = struct_align.max(effective_align);
262
263 out_fields.push(Field {
264 name: fname.clone(),
265 ty: type_info,
266 offset,
267 size,
268 align: effective_align,
269 source_file: None,
270 source_line: None,
271 access: AccessPattern::Unknown,
272 });
273 offset += size;
274 }
275
276 if let Some(fa) = forced_align
278 && fa > struct_align
279 {
280 struct_align = fa;
281 }
282
283 if !packed && struct_align > 0 {
284 offset = offset.next_multiple_of(struct_align);
285 }
286
287 StructLayout {
288 name,
289 total_size: offset,
290 align: struct_align,
291 fields: out_fields,
292 source_file: None,
293 source_line: None,
294 arch,
295 is_packed: packed,
296 is_union: false,
297 is_repr_rust: false, suppressed_findings: Vec::new(), }
300}
301
302struct StructVisitor<'src> {
305 arch: &'static ArchConfig,
306 layouts: Vec<StructLayout>,
307 source: &'src str,
308}
309
310impl<'ast, 'src> Visit<'ast> for StructVisitor<'src> {
311 fn visit_item_struct(&mut self, node: &'ast ItemStruct) {
312 syn::visit::visit_item_struct(self, node); if !node.generics.params.is_empty() {
318 return;
319 }
320
321 let name = node.ident.to_string();
322 let packed = is_packed(&node.attrs);
323 let forced_align = repr_align(&node.attrs);
324
325 let fields: Vec<(String, Type, Option<String>, u32)> = match &node.fields {
327 Fields::Named(nf) => nf
328 .named
329 .iter()
330 .map(|f| {
331 let fname = f.ident.as_ref().map(|i| i.to_string()).unwrap_or_default();
332 let guard = extract_guard_from_attrs(&f.attrs);
333 let line = f
334 .ident
335 .as_ref()
336 .map(|i| i.span().start().line as u32)
337 .unwrap_or(0);
338 (fname, f.ty.clone(), guard, line)
339 })
340 .collect(),
341 Fields::Unnamed(uf) => uf
342 .unnamed
343 .iter()
344 .enumerate()
345 .map(|(i, f)| {
346 let guard = extract_guard_from_attrs(&f.attrs);
347 (format!("_{i}"), f.ty.clone(), guard, 0u32)
349 })
350 .collect(),
351 Fields::Unit => vec![],
352 };
353
354 let name_ty: Vec<(String, Type)> = fields
355 .iter()
356 .map(|(n, t, _, _)| (n.clone(), t.clone()))
357 .collect();
358 let mut layout = simulate_rust_layout(name, &name_ty, packed, forced_align, self.arch);
359 let struct_line = node.ident.span().start().line as u32;
360 layout.source_line = Some(struct_line);
361 layout.is_repr_rust = is_repr_rust(&node.attrs);
362 layout.suppressed_findings =
363 super::suppress::suppressed_from_source_line(self.source, struct_line);
364
365 for (i, (_, _, guard, field_line)) in fields.iter().enumerate() {
367 if *field_line > 0 {
368 layout.fields[i].source_line = Some(*field_line);
369 }
370 if let Some(g) = guard {
371 layout.fields[i].access = AccessPattern::Concurrent {
372 guard: Some(g.clone()),
373 is_atomic: false,
374 is_annotated: true,
375 };
376 }
377 }
378
379 self.layouts.push(layout);
380 }
381
382 fn visit_item_enum(&mut self, node: &'ast ItemEnum) {
383 syn::visit::visit_item_enum(self, node);
384
385 if !node.generics.params.is_empty() {
387 return;
388 }
389
390 let name = node.ident.to_string();
391 let n_variants = node.variants.len();
392 if n_variants == 0 {
393 return;
394 }
395
396 let disc_size: usize = if n_variants <= 256 {
399 1
400 } else if n_variants <= 65536 {
401 2
402 } else {
403 4
404 };
405
406 let all_unit = node
408 .variants
409 .iter()
410 .all(|v| matches!(v.fields, Fields::Unit));
411
412 if all_unit {
413 let enum_line = node.ident.span().start().line as u32;
415 let layout = StructLayout {
416 name,
417 total_size: disc_size,
418 align: disc_size,
419 fields: vec![Field {
420 name: "__discriminant".to_string(),
421 ty: TypeInfo::Primitive {
422 name: format!("u{}", disc_size * 8),
423 size: disc_size,
424 align: disc_size,
425 },
426 offset: 0,
427 size: disc_size,
428 align: disc_size,
429 source_file: None,
430 source_line: None,
431 access: AccessPattern::Unknown,
432 }],
433 source_file: None,
434 source_line: Some(enum_line),
435 arch: self.arch,
436 is_packed: false,
437 is_union: false,
438 is_repr_rust: is_repr_rust(&node.attrs),
439 suppressed_findings: super::suppress::suppressed_from_source_line(
440 self.source,
441 enum_line,
442 ),
443 };
444 self.layouts.push(layout);
445 return;
446 }
447
448 let mut max_payload_size = 0usize;
450 let mut max_payload_align = 1usize;
451
452 for variant in &node.variants {
453 let var_fields: Vec<(String, Type)> = match &variant.fields {
454 Fields::Named(nf) => nf
455 .named
456 .iter()
457 .map(|f| {
458 let n = f.ident.as_ref().map(|i| i.to_string()).unwrap_or_default();
459 (n, f.ty.clone())
460 })
461 .collect(),
462 Fields::Unnamed(uf) => uf
463 .unnamed
464 .iter()
465 .enumerate()
466 .map(|(i, f)| (format!("_{i}"), f.ty.clone()))
467 .collect(),
468 Fields::Unit => vec![],
469 };
470
471 if !var_fields.is_empty() {
472 let var_layout =
473 simulate_rust_layout(String::new(), &var_fields, false, None, self.arch);
474 if var_layout.total_size > max_payload_size {
475 max_payload_size = var_layout.total_size;
476 }
477 max_payload_align = max_payload_align.max(var_layout.align);
478 }
479 }
480
481 let payload_align = max_payload_align.max(1);
485 let disc_offset = max_payload_size;
486 let total_before_pad = disc_offset + disc_size;
487 let total_align = payload_align.max(disc_size);
488 let total_size = total_before_pad.next_multiple_of(total_align);
489
490 let mut fields: Vec<Field> = Vec::new();
491 if max_payload_size > 0 {
492 fields.push(Field {
493 name: "__payload".to_string(),
494 ty: TypeInfo::Opaque {
495 name: format!("largest_variant_payload ({}B)", max_payload_size),
496 size: max_payload_size,
497 align: payload_align,
498 },
499 offset: 0,
500 size: max_payload_size,
501 align: payload_align,
502 source_file: None,
503 source_line: None,
504 access: AccessPattern::Unknown,
505 });
506 }
507 fields.push(Field {
508 name: "__discriminant".to_string(),
509 ty: TypeInfo::Primitive {
510 name: format!("u{}", disc_size * 8),
511 size: disc_size,
512 align: disc_size,
513 },
514 offset: disc_offset,
515 size: disc_size,
516 align: disc_size,
517 source_file: None,
518 source_line: None,
519 access: AccessPattern::Unknown,
520 });
521
522 let enum_line = node.ident.span().start().line as u32;
523 self.layouts.push(StructLayout {
524 name,
525 total_size,
526 align: total_align,
527 fields,
528 source_file: None,
529 source_line: Some(enum_line),
530 arch: self.arch,
531 is_packed: false,
532 is_union: false,
533 is_repr_rust: is_repr_rust(&node.attrs),
534 suppressed_findings: super::suppress::suppressed_from_source_line(
535 self.source,
536 enum_line,
537 ),
538 });
539 }
540}
541
542pub fn parse_rust(source: &str, arch: &'static ArchConfig) -> anyhow::Result<Vec<StructLayout>> {
545 let file: syn::File = syn::parse_str(source)?;
546 let mut visitor = StructVisitor {
547 arch,
548 layouts: Vec::new(),
549 source,
550 };
551 visitor.visit_file(&file);
552 Ok(visitor.layouts)
553}
554
555#[cfg(test)]
558mod tests {
559 use super::*;
560 use padlock_core::arch::X86_64_SYSV;
561
562 #[test]
563 fn parse_simple_struct() {
564 let src = "struct Foo { a: u8, b: u64, c: u32 }";
565 let layouts = parse_rust(src, &X86_64_SYSV).unwrap();
566 assert_eq!(layouts.len(), 1);
567 let l = &layouts[0];
568 assert_eq!(l.name, "Foo");
569 assert_eq!(l.fields.len(), 3);
570 assert_eq!(l.fields[0].size, 1); assert_eq!(l.fields[1].size, 8); assert_eq!(l.fields[2].size, 4); }
574
575 #[test]
576 fn layout_includes_padding() {
577 let src = "struct T { a: u8, b: u64 }";
579 let layouts = parse_rust(src, &X86_64_SYSV).unwrap();
580 let l = &layouts[0];
581 assert_eq!(l.fields[0].offset, 0);
582 assert_eq!(l.fields[1].offset, 8); assert_eq!(l.total_size, 16);
584 let gaps = padlock_core::ir::find_padding(l);
585 assert_eq!(gaps[0].bytes, 7);
586 }
587
588 #[test]
589 fn multiple_structs_parsed() {
590 let src = "struct A { x: u32 } struct B { y: u64 }";
591 let layouts = parse_rust(src, &X86_64_SYSV).unwrap();
592 assert_eq!(layouts.len(), 2);
593 }
594
595 #[test]
596 fn packed_struct_no_padding() {
597 let src = "#[repr(packed)] struct P { a: u8, b: u64 }";
598 let layouts = parse_rust(src, &X86_64_SYSV).unwrap();
599 let l = &layouts[0];
600 assert!(l.is_packed);
601 assert_eq!(l.fields[1].offset, 1); let gaps = padlock_core::ir::find_padding(l);
603 assert!(gaps.is_empty());
604 }
605
606 #[test]
607 fn pointer_field_uses_arch_size() {
608 let src = "struct S { p: *const u8 }";
609 let layouts = parse_rust(src, &X86_64_SYSV).unwrap();
610 assert_eq!(layouts[0].fields[0].size, 8); }
612
613 #[test]
616 fn lock_protected_by_attr_sets_guard() {
617 let src = r#"
618struct Cache {
619 #[lock_protected_by = "mu"]
620 readers: u64,
621 mu: u64,
622}
623"#;
624 let layouts = parse_rust(src, &X86_64_SYSV).unwrap();
625 let readers = &layouts[0].fields[0];
626 assert_eq!(readers.name, "readers");
627 if let AccessPattern::Concurrent { guard, .. } = &readers.access {
628 assert_eq!(guard.as_deref(), Some("mu"));
629 } else {
630 panic!("expected Concurrent, got {:?}", readers.access);
631 }
632 }
633
634 #[test]
635 fn guarded_by_string_attr_sets_guard() {
636 let src = r#"
637struct S {
638 #[guarded_by("lock")]
639 value: u32,
640}
641"#;
642 let layouts = parse_rust(src, &X86_64_SYSV).unwrap();
643 if let AccessPattern::Concurrent { guard, .. } = &layouts[0].fields[0].access {
644 assert_eq!(guard.as_deref(), Some("lock"));
645 } else {
646 panic!("expected Concurrent");
647 }
648 }
649
650 #[test]
651 fn guarded_by_ident_attr_sets_guard() {
652 let src = r#"
653struct S {
654 #[guarded_by(mu)]
655 count: u64,
656}
657"#;
658 let layouts = parse_rust(src, &X86_64_SYSV).unwrap();
659 if let AccessPattern::Concurrent { guard, .. } = &layouts[0].fields[0].access {
660 assert_eq!(guard.as_deref(), Some("mu"));
661 } else {
662 panic!("expected Concurrent");
663 }
664 }
665
666 #[test]
667 fn protected_by_attr_sets_guard() {
668 let src = r#"
669struct S {
670 #[protected_by = "lock_a"]
671 x: u64,
672}
673"#;
674 let layouts = parse_rust(src, &X86_64_SYSV).unwrap();
675 if let AccessPattern::Concurrent { guard, .. } = &layouts[0].fields[0].access {
676 assert_eq!(guard.as_deref(), Some("lock_a"));
677 } else {
678 panic!("expected Concurrent");
679 }
680 }
681
682 #[test]
683 fn different_guards_on_same_cache_line_is_false_sharing() {
684 let src = r#"
687struct HotPath {
688 #[lock_protected_by = "mu_a"]
689 readers: u64,
690 #[lock_protected_by = "mu_b"]
691 writers: u64,
692}
693"#;
694 let layouts = parse_rust(src, &X86_64_SYSV).unwrap();
695 assert!(padlock_core::analysis::false_sharing::has_false_sharing(
696 &layouts[0]
697 ));
698 }
699
700 #[test]
701 fn same_guard_on_same_cache_line_is_not_false_sharing() {
702 let src = r#"
703struct Safe {
704 #[lock_protected_by = "mu"]
705 a: u64,
706 #[lock_protected_by = "mu"]
707 b: u64,
708}
709"#;
710 let layouts = parse_rust(src, &X86_64_SYSV).unwrap();
711 assert!(!padlock_core::analysis::false_sharing::has_false_sharing(
712 &layouts[0]
713 ));
714 }
715
716 #[test]
717 fn unannotated_field_stays_unknown() {
718 let src = "struct S { x: u64 }";
719 let layouts = parse_rust(src, &X86_64_SYSV).unwrap();
720 assert!(matches!(
721 layouts[0].fields[0].access,
722 AccessPattern::Unknown
723 ));
724 }
725
726 #[test]
729 fn vec_field_has_three_pointer_size() {
730 let src = "struct S { items: Vec<u64> }";
732 let layouts = parse_rust(src, &X86_64_SYSV).unwrap();
733 assert_eq!(layouts[0].fields[0].size, 24); }
735
736 #[test]
737 fn string_field_has_three_pointer_size() {
738 let src = "struct S { name: String }";
739 let layouts = parse_rust(src, &X86_64_SYSV).unwrap();
740 assert_eq!(layouts[0].fields[0].size, 24);
741 }
742
743 #[test]
744 fn box_field_has_pointer_size() {
745 let src = "struct S { inner: Box<u64> }";
746 let layouts = parse_rust(src, &X86_64_SYSV).unwrap();
747 assert_eq!(layouts[0].fields[0].size, 8);
748 }
749
750 #[test]
751 fn arc_field_has_pointer_size() {
752 let src = "struct S { shared: Arc<Vec<u8>> }";
753 let layouts = parse_rust(src, &X86_64_SYSV).unwrap();
754 assert_eq!(layouts[0].fields[0].size, 8);
755 }
756
757 #[test]
758 fn phantom_data_is_zero_sized() {
759 let src = "struct S { a: u64, _marker: PhantomData<u8> }";
760 let layouts = parse_rust(src, &X86_64_SYSV).unwrap();
761 let marker = layouts[0]
762 .fields
763 .iter()
764 .find(|f| f.name == "_marker")
765 .unwrap();
766 assert_eq!(marker.size, 0);
767 }
768
769 #[test]
770 fn duration_field_is_16_bytes() {
771 let src = "struct S { timeout: Duration }";
772 let layouts = parse_rust(src, &X86_64_SYSV).unwrap();
773 assert_eq!(layouts[0].fields[0].size, 16);
774 }
775
776 #[test]
777 fn atomic_u64_has_correct_size() {
778 let src = "struct S { counter: AtomicU64 }";
779 let layouts = parse_rust(src, &X86_64_SYSV).unwrap();
780 assert_eq!(layouts[0].fields[0].size, 8);
781 }
782
783 #[test]
784 fn atomic_bool_has_correct_size() {
785 let src = "struct S { flag: AtomicBool }";
786 let layouts = parse_rust(src, &X86_64_SYSV).unwrap();
787 assert_eq!(layouts[0].fields[0].size, 1);
788 }
789
790 #[test]
793 fn generic_struct_is_skipped() {
794 let src = "struct Wrapper<T> { value: T, count: usize }";
796 let layouts = parse_rust(src, &X86_64_SYSV).unwrap();
797 assert!(
798 layouts.is_empty(),
799 "generic structs should be skipped; got {:?}",
800 layouts.iter().map(|l| &l.name).collect::<Vec<_>>()
801 );
802 }
803
804 #[test]
805 fn generic_struct_with_multiple_params_is_skipped() {
806 let src = "struct Pair<A, B> { first: A, second: B }";
807 let layouts = parse_rust(src, &X86_64_SYSV).unwrap();
808 assert!(layouts.is_empty());
809 }
810
811 #[test]
812 fn non_generic_struct_still_parsed_when_generic_sibling_exists() {
813 let src = r#"
814struct Generic<T> { value: T }
815struct Concrete { a: u32, b: u64 }
816"#;
817 let layouts = parse_rust(src, &X86_64_SYSV).unwrap();
818 assert_eq!(layouts.len(), 1);
819 assert_eq!(layouts[0].name, "Concrete");
820 }
821
822 #[test]
825 fn unit_enum_is_just_discriminant() {
826 let src = "enum Color { Red, Green, Blue }";
827 let layouts = parse_rust(src, &X86_64_SYSV).unwrap();
828 assert_eq!(layouts.len(), 1);
829 let l = &layouts[0];
830 assert_eq!(l.name, "Color");
831 assert_eq!(l.total_size, 1); assert_eq!(l.fields.len(), 1);
833 assert_eq!(l.fields[0].name, "__discriminant");
834 }
835
836 #[test]
837 fn unit_enum_with_many_variants_uses_u16_discriminant() {
838 let variants: String = (0..300)
840 .map(|i| format!("V{i}"))
841 .collect::<Vec<_>>()
842 .join(", ");
843 let src = format!("enum Big {{ {variants} }}");
844 let layouts = parse_rust(&src, &X86_64_SYSV).unwrap();
845 let l = &layouts[0];
846 assert_eq!(l.total_size, 2); assert_eq!(l.fields[0].size, 2);
848 }
849
850 #[test]
851 fn data_enum_total_size_covers_largest_variant() {
852 let src = r#"
855enum Message {
856 Quit,
857 Move { x: i32, y: i32 },
858 Write(String),
859}
860"#;
861 let layouts = parse_rust(src, &X86_64_SYSV).unwrap();
862 let l = &layouts[0];
863 assert_eq!(l.name, "Message");
864 assert_eq!(l.total_size, 32);
866 assert_eq!(l.fields.len(), 2);
867 let payload = l.fields.iter().find(|f| f.name == "__payload").unwrap();
868 assert_eq!(payload.size, 24); }
870
871 #[test]
872 fn generic_enum_is_skipped() {
873 let src = "enum Wrapper<T> { Some(T), None }";
874 let layouts = parse_rust(src, &X86_64_SYSV).unwrap();
875 assert!(
876 layouts.is_empty(),
877 "generic enums should be skipped; got {:?}",
878 layouts.iter().map(|l| &l.name).collect::<Vec<_>>()
879 );
880 }
881
882 #[test]
883 fn empty_enum_is_skipped() {
884 let src = "enum Never {}";
885 let layouts = parse_rust(src, &X86_64_SYSV).unwrap();
886 assert!(layouts.is_empty());
887 }
888
889 #[test]
890 fn enum_with_only_unit_variants_has_no_payload_field() {
891 let src = "enum Dir { North, South, East, West }";
892 let layouts = parse_rust(src, &X86_64_SYSV).unwrap();
893 assert!(!layouts[0].fields.iter().any(|f| f.name == "__payload"));
894 }
895
896 #[test]
897 fn data_enum_and_sibling_struct_both_parsed() {
898 let src = r#"
899enum Status { Ok, Err(u32) }
900struct Conn { port: u16, status: u32 }
901"#;
902 let layouts = parse_rust(src, &X86_64_SYSV).unwrap();
903 assert_eq!(layouts.len(), 2);
904 assert!(layouts.iter().any(|l| l.name == "Status"));
905 assert!(layouts.iter().any(|l| l.name == "Conn"));
906 }
907
908 #[test]
911 fn enum_with_only_zero_sized_variants_has_payload_size_zero() {
912 let src = "enum E { A, B }";
914 let layouts = parse_rust(src, &X86_64_SYSV).unwrap();
915 let l = &layouts[0];
916 assert_eq!(l.total_size, 1);
917 }
918
919 #[test]
920 fn enum_mixed_unit_and_data_includes_max_payload() {
921 let src = "enum E { Nothing, Data(u64) }";
923 let layouts = parse_rust(src, &X86_64_SYSV).unwrap();
924 let l = &layouts[0];
925 let payload = l.fields.iter().find(|f| f.name == "__payload").unwrap();
926 assert_eq!(payload.size, 8); }
928
929 #[test]
932 fn repr_align_raises_struct_alignment() {
933 let src = "#[repr(align(64))]\nstruct CacheLine { a: u8, b: u32 }";
934 let layouts = parse_rust(src, &X86_64_SYSV).unwrap();
935 let l = &layouts[0];
936 assert_eq!(
937 l.align, 64,
938 "repr(align(64)) must set struct alignment to 64"
939 );
940 assert_eq!(l.total_size, 64, "size must be padded to 64 bytes");
941 }
942
943 #[test]
944 fn repr_align_does_not_shrink_natural_alignment() {
945 let src = "#[repr(align(1))]\nstruct S { a: u64 }";
947 let layouts = parse_rust(src, &X86_64_SYSV).unwrap();
948 let l = &layouts[0];
949 assert_eq!(
950 l.align, 8,
951 "natural align must not be reduced below repr(align)"
952 );
953 }
954
955 #[test]
956 fn repr_align_adds_trailing_padding() {
957 let src = "#[repr(align(8))]\nstruct S { a: u8, b: u32 }";
959 let layouts = parse_rust(src, &X86_64_SYSV).unwrap();
960 let l = &layouts[0];
961 assert_eq!(l.total_size, 8);
962 }
963
964 #[test]
965 fn no_repr_align_has_natural_size() {
966 let src = "struct S { a: u8, b: u32 }";
968 let layouts = parse_rust(src, &X86_64_SYSV).unwrap();
969 let l = &layouts[0];
970 assert_eq!(l.total_size, 8);
972 assert_eq!(l.align, 4);
973 }
974
975 #[test]
978 fn tuple_struct_fields_named_by_index() {
979 let src = "struct Pair(u64, u8);";
980 let layouts = parse_rust(src, &X86_64_SYSV).unwrap();
981 let l = &layouts[0];
982 assert_eq!(l.fields[0].name, "_0");
983 assert_eq!(l.fields[1].name, "_1");
984 }
985
986 #[test]
987 fn tuple_struct_layout_follows_alignment() {
988 let src = "struct S(u64, u8);";
990 let layouts = parse_rust(src, &X86_64_SYSV).unwrap();
991 let l = &layouts[0];
992 assert_eq!(l.fields[0].offset, 0);
993 assert_eq!(l.fields[0].size, 8);
994 assert_eq!(l.fields[1].offset, 8);
995 assert_eq!(l.fields[1].size, 1);
996 assert_eq!(l.total_size, 16);
997 }
998
999 #[test]
1000 fn tuple_struct_with_padding_waste_detected() {
1001 let src = "struct S(u8, u64);";
1003 let layouts = parse_rust(src, &X86_64_SYSV).unwrap();
1004 let l = &layouts[0];
1005 assert_eq!(l.fields[0].offset, 0); assert_eq!(l.fields[1].offset, 8); assert_eq!(l.total_size, 16);
1008 let gaps = padlock_core::ir::find_padding(l);
1009 assert_eq!(gaps[0].bytes, 7);
1010 }
1011
1012 #[test]
1015 fn nonzero_types_same_size_as_base() {
1016 assert_eq!(primitive_size_align("NonZeroU8", &X86_64_SYSV), (1, 1));
1017 assert_eq!(primitive_size_align("NonZeroI8", &X86_64_SYSV), (1, 1));
1018 assert_eq!(primitive_size_align("NonZeroU16", &X86_64_SYSV), (2, 2));
1019 assert_eq!(primitive_size_align("NonZeroU32", &X86_64_SYSV), (4, 4));
1020 assert_eq!(primitive_size_align("NonZeroU64", &X86_64_SYSV), (8, 8));
1021 assert_eq!(primitive_size_align("NonZeroU128", &X86_64_SYSV), (16, 16));
1022 assert_eq!(
1023 primitive_size_align("NonZeroUsize", &X86_64_SYSV),
1024 (X86_64_SYSV.pointer_size, X86_64_SYSV.pointer_size)
1025 );
1026 }
1027
1028 #[test]
1029 fn float16_and_float128_correct_size() {
1030 assert_eq!(primitive_size_align("f16", &X86_64_SYSV), (2, 2));
1031 assert_eq!(primitive_size_align("f128", &X86_64_SYSV), (16, 16));
1032 }
1033
1034 #[test]
1035 fn rust_struct_with_nonzero_fields() {
1036 let src = "struct Counts { hits: NonZeroU64, misses: NonZeroU32, flags: u8 }";
1037 let layouts = parse_rust(src, &X86_64_SYSV).unwrap();
1038 let l = &layouts[0];
1039 assert_eq!(l.fields[0].size, 8); assert_eq!(l.fields[1].size, 4); assert_eq!(l.fields[2].size, 1); assert_eq!(l.total_size, 16);
1044 }
1045
1046 #[test]
1049 fn plain_struct_is_repr_rust() {
1050 let src = "struct Foo { a: u64, b: u32 }";
1051 let layouts = parse_rust(src, &X86_64_SYSV).unwrap();
1052 assert!(layouts[0].is_repr_rust, "plain struct should be repr(Rust)");
1053 }
1054
1055 #[test]
1056 fn repr_c_struct_is_not_repr_rust() {
1057 let src = "#[repr(C)] struct Foo { a: u64, b: u32 }";
1058 let layouts = parse_rust(src, &X86_64_SYSV).unwrap();
1059 assert!(
1060 !layouts[0].is_repr_rust,
1061 "repr(C) struct must not be repr(Rust)"
1062 );
1063 }
1064
1065 #[test]
1066 fn repr_packed_struct_is_not_repr_rust() {
1067 let src = "#[repr(packed)] struct Foo { a: u64, b: u32 }";
1068 let layouts = parse_rust(src, &X86_64_SYSV).unwrap();
1069 assert!(
1070 !layouts[0].is_repr_rust,
1071 "repr(packed) struct must not be repr(Rust)"
1072 );
1073 }
1074
1075 #[test]
1076 fn repr_transparent_struct_is_not_repr_rust() {
1077 let src = "#[repr(transparent)] struct Wrapper(u64);";
1078 let layouts = parse_rust(src, &X86_64_SYSV).unwrap();
1079 assert!(
1080 !layouts[0].is_repr_rust,
1081 "repr(transparent) struct must not be repr(Rust)"
1082 );
1083 }
1084
1085 #[test]
1086 fn plain_enum_is_repr_rust() {
1087 let src = "enum Color { Red, Green, Blue }";
1088 let layouts = parse_rust(src, &X86_64_SYSV).unwrap();
1089 assert!(layouts[0].is_repr_rust, "plain enum should be repr(Rust)");
1090 }
1091
1092 #[test]
1093 fn repr_c_enum_is_not_repr_rust() {
1094 let src = "#[repr(C)] enum Dir { North, South }";
1095 let layouts = parse_rust(src, &X86_64_SYSV).unwrap();
1096 assert!(
1097 !layouts[0].is_repr_rust,
1098 "repr(C) enum must not be repr(Rust)"
1099 );
1100 }
1101}