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, }
299}
300
301struct StructVisitor {
304 arch: &'static ArchConfig,
305 layouts: Vec<StructLayout>,
306}
307
308impl<'ast> Visit<'ast> for StructVisitor {
309 fn visit_item_struct(&mut self, node: &'ast ItemStruct) {
310 syn::visit::visit_item_struct(self, node); if !node.generics.params.is_empty() {
316 return;
317 }
318
319 let name = node.ident.to_string();
320 let packed = is_packed(&node.attrs);
321 let forced_align = repr_align(&node.attrs);
322
323 let fields: Vec<(String, Type, Option<String>)> = match &node.fields {
325 Fields::Named(nf) => nf
326 .named
327 .iter()
328 .map(|f| {
329 let fname = f.ident.as_ref().map(|i| i.to_string()).unwrap_or_default();
330 let guard = extract_guard_from_attrs(&f.attrs);
331 (fname, f.ty.clone(), guard)
332 })
333 .collect(),
334 Fields::Unnamed(uf) => uf
335 .unnamed
336 .iter()
337 .enumerate()
338 .map(|(i, f)| {
339 let guard = extract_guard_from_attrs(&f.attrs);
340 (format!("_{i}"), f.ty.clone(), guard)
341 })
342 .collect(),
343 Fields::Unit => vec![],
344 };
345
346 let name_ty: Vec<(String, Type)> = fields
347 .iter()
348 .map(|(n, t, _)| (n.clone(), t.clone()))
349 .collect();
350 let mut layout = simulate_rust_layout(name, &name_ty, packed, forced_align, self.arch);
351 layout.source_line = Some(node.ident.span().start().line as u32);
352 layout.is_repr_rust = is_repr_rust(&node.attrs);
353
354 for (i, (_, _, guard)) in fields.iter().enumerate() {
357 if let Some(g) = guard {
358 layout.fields[i].access = AccessPattern::Concurrent {
359 guard: Some(g.clone()),
360 is_atomic: false,
361 };
362 }
363 }
364
365 self.layouts.push(layout);
366 }
367
368 fn visit_item_enum(&mut self, node: &'ast ItemEnum) {
369 syn::visit::visit_item_enum(self, node);
370
371 if !node.generics.params.is_empty() {
373 return;
374 }
375
376 let name = node.ident.to_string();
377 let n_variants = node.variants.len();
378 if n_variants == 0 {
379 return;
380 }
381
382 let disc_size: usize = if n_variants <= 256 {
385 1
386 } else if n_variants <= 65536 {
387 2
388 } else {
389 4
390 };
391
392 let all_unit = node
394 .variants
395 .iter()
396 .all(|v| matches!(v.fields, Fields::Unit));
397
398 if all_unit {
399 let layout = StructLayout {
401 name,
402 total_size: disc_size,
403 align: disc_size,
404 fields: vec![Field {
405 name: "__discriminant".to_string(),
406 ty: TypeInfo::Primitive {
407 name: format!("u{}", disc_size * 8),
408 size: disc_size,
409 align: disc_size,
410 },
411 offset: 0,
412 size: disc_size,
413 align: disc_size,
414 source_file: None,
415 source_line: None,
416 access: AccessPattern::Unknown,
417 }],
418 source_file: None,
419 source_line: Some(node.ident.span().start().line as u32),
420 arch: self.arch,
421 is_packed: false,
422 is_union: false,
423 is_repr_rust: is_repr_rust(&node.attrs),
424 };
425 self.layouts.push(layout);
426 return;
427 }
428
429 let mut max_payload_size = 0usize;
431 let mut max_payload_align = 1usize;
432
433 for variant in &node.variants {
434 let var_fields: Vec<(String, Type)> = match &variant.fields {
435 Fields::Named(nf) => nf
436 .named
437 .iter()
438 .map(|f| {
439 let n = f.ident.as_ref().map(|i| i.to_string()).unwrap_or_default();
440 (n, f.ty.clone())
441 })
442 .collect(),
443 Fields::Unnamed(uf) => uf
444 .unnamed
445 .iter()
446 .enumerate()
447 .map(|(i, f)| (format!("_{i}"), f.ty.clone()))
448 .collect(),
449 Fields::Unit => vec![],
450 };
451
452 if !var_fields.is_empty() {
453 let var_layout =
454 simulate_rust_layout(String::new(), &var_fields, false, None, self.arch);
455 if var_layout.total_size > max_payload_size {
456 max_payload_size = var_layout.total_size;
457 }
458 max_payload_align = max_payload_align.max(var_layout.align);
459 }
460 }
461
462 let payload_align = max_payload_align.max(1);
466 let disc_offset = max_payload_size;
467 let total_before_pad = disc_offset + disc_size;
468 let total_align = payload_align.max(disc_size);
469 let total_size = total_before_pad.next_multiple_of(total_align);
470
471 let mut fields: Vec<Field> = Vec::new();
472 if max_payload_size > 0 {
473 fields.push(Field {
474 name: "__payload".to_string(),
475 ty: TypeInfo::Opaque {
476 name: format!("largest_variant_payload ({}B)", max_payload_size),
477 size: max_payload_size,
478 align: payload_align,
479 },
480 offset: 0,
481 size: max_payload_size,
482 align: payload_align,
483 source_file: None,
484 source_line: None,
485 access: AccessPattern::Unknown,
486 });
487 }
488 fields.push(Field {
489 name: "__discriminant".to_string(),
490 ty: TypeInfo::Primitive {
491 name: format!("u{}", disc_size * 8),
492 size: disc_size,
493 align: disc_size,
494 },
495 offset: disc_offset,
496 size: disc_size,
497 align: disc_size,
498 source_file: None,
499 source_line: None,
500 access: AccessPattern::Unknown,
501 });
502
503 self.layouts.push(StructLayout {
504 name,
505 total_size,
506 align: total_align,
507 fields,
508 source_file: None,
509 source_line: Some(node.ident.span().start().line as u32),
510 arch: self.arch,
511 is_packed: false,
512 is_union: false,
513 is_repr_rust: is_repr_rust(&node.attrs),
514 });
515 }
516}
517
518pub fn parse_rust(source: &str, arch: &'static ArchConfig) -> anyhow::Result<Vec<StructLayout>> {
521 let file: syn::File = syn::parse_str(source)?;
522 let mut visitor = StructVisitor {
523 arch,
524 layouts: Vec::new(),
525 };
526 visitor.visit_file(&file);
527 Ok(visitor.layouts)
528}
529
530#[cfg(test)]
533mod tests {
534 use super::*;
535 use padlock_core::arch::X86_64_SYSV;
536
537 #[test]
538 fn parse_simple_struct() {
539 let src = "struct Foo { a: u8, b: u64, c: u32 }";
540 let layouts = parse_rust(src, &X86_64_SYSV).unwrap();
541 assert_eq!(layouts.len(), 1);
542 let l = &layouts[0];
543 assert_eq!(l.name, "Foo");
544 assert_eq!(l.fields.len(), 3);
545 assert_eq!(l.fields[0].size, 1); assert_eq!(l.fields[1].size, 8); assert_eq!(l.fields[2].size, 4); }
549
550 #[test]
551 fn layout_includes_padding() {
552 let src = "struct T { a: u8, b: u64 }";
554 let layouts = parse_rust(src, &X86_64_SYSV).unwrap();
555 let l = &layouts[0];
556 assert_eq!(l.fields[0].offset, 0);
557 assert_eq!(l.fields[1].offset, 8); assert_eq!(l.total_size, 16);
559 let gaps = padlock_core::ir::find_padding(l);
560 assert_eq!(gaps[0].bytes, 7);
561 }
562
563 #[test]
564 fn multiple_structs_parsed() {
565 let src = "struct A { x: u32 } struct B { y: u64 }";
566 let layouts = parse_rust(src, &X86_64_SYSV).unwrap();
567 assert_eq!(layouts.len(), 2);
568 }
569
570 #[test]
571 fn packed_struct_no_padding() {
572 let src = "#[repr(packed)] struct P { a: u8, b: u64 }";
573 let layouts = parse_rust(src, &X86_64_SYSV).unwrap();
574 let l = &layouts[0];
575 assert!(l.is_packed);
576 assert_eq!(l.fields[1].offset, 1); let gaps = padlock_core::ir::find_padding(l);
578 assert!(gaps.is_empty());
579 }
580
581 #[test]
582 fn pointer_field_uses_arch_size() {
583 let src = "struct S { p: *const u8 }";
584 let layouts = parse_rust(src, &X86_64_SYSV).unwrap();
585 assert_eq!(layouts[0].fields[0].size, 8); }
587
588 #[test]
591 fn lock_protected_by_attr_sets_guard() {
592 let src = r#"
593struct Cache {
594 #[lock_protected_by = "mu"]
595 readers: u64,
596 mu: u64,
597}
598"#;
599 let layouts = parse_rust(src, &X86_64_SYSV).unwrap();
600 let readers = &layouts[0].fields[0];
601 assert_eq!(readers.name, "readers");
602 if let AccessPattern::Concurrent { guard, .. } = &readers.access {
603 assert_eq!(guard.as_deref(), Some("mu"));
604 } else {
605 panic!("expected Concurrent, got {:?}", readers.access);
606 }
607 }
608
609 #[test]
610 fn guarded_by_string_attr_sets_guard() {
611 let src = r#"
612struct S {
613 #[guarded_by("lock")]
614 value: u32,
615}
616"#;
617 let layouts = parse_rust(src, &X86_64_SYSV).unwrap();
618 if let AccessPattern::Concurrent { guard, .. } = &layouts[0].fields[0].access {
619 assert_eq!(guard.as_deref(), Some("lock"));
620 } else {
621 panic!("expected Concurrent");
622 }
623 }
624
625 #[test]
626 fn guarded_by_ident_attr_sets_guard() {
627 let src = r#"
628struct S {
629 #[guarded_by(mu)]
630 count: u64,
631}
632"#;
633 let layouts = parse_rust(src, &X86_64_SYSV).unwrap();
634 if let AccessPattern::Concurrent { guard, .. } = &layouts[0].fields[0].access {
635 assert_eq!(guard.as_deref(), Some("mu"));
636 } else {
637 panic!("expected Concurrent");
638 }
639 }
640
641 #[test]
642 fn protected_by_attr_sets_guard() {
643 let src = r#"
644struct S {
645 #[protected_by = "lock_a"]
646 x: u64,
647}
648"#;
649 let layouts = parse_rust(src, &X86_64_SYSV).unwrap();
650 if let AccessPattern::Concurrent { guard, .. } = &layouts[0].fields[0].access {
651 assert_eq!(guard.as_deref(), Some("lock_a"));
652 } else {
653 panic!("expected Concurrent");
654 }
655 }
656
657 #[test]
658 fn different_guards_on_same_cache_line_is_false_sharing() {
659 let src = r#"
662struct HotPath {
663 #[lock_protected_by = "mu_a"]
664 readers: u64,
665 #[lock_protected_by = "mu_b"]
666 writers: u64,
667}
668"#;
669 let layouts = parse_rust(src, &X86_64_SYSV).unwrap();
670 assert!(padlock_core::analysis::false_sharing::has_false_sharing(
671 &layouts[0]
672 ));
673 }
674
675 #[test]
676 fn same_guard_on_same_cache_line_is_not_false_sharing() {
677 let src = r#"
678struct Safe {
679 #[lock_protected_by = "mu"]
680 a: u64,
681 #[lock_protected_by = "mu"]
682 b: u64,
683}
684"#;
685 let layouts = parse_rust(src, &X86_64_SYSV).unwrap();
686 assert!(!padlock_core::analysis::false_sharing::has_false_sharing(
687 &layouts[0]
688 ));
689 }
690
691 #[test]
692 fn unannotated_field_stays_unknown() {
693 let src = "struct S { x: u64 }";
694 let layouts = parse_rust(src, &X86_64_SYSV).unwrap();
695 assert!(matches!(
696 layouts[0].fields[0].access,
697 AccessPattern::Unknown
698 ));
699 }
700
701 #[test]
704 fn vec_field_has_three_pointer_size() {
705 let src = "struct S { items: Vec<u64> }";
707 let layouts = parse_rust(src, &X86_64_SYSV).unwrap();
708 assert_eq!(layouts[0].fields[0].size, 24); }
710
711 #[test]
712 fn string_field_has_three_pointer_size() {
713 let src = "struct S { name: String }";
714 let layouts = parse_rust(src, &X86_64_SYSV).unwrap();
715 assert_eq!(layouts[0].fields[0].size, 24);
716 }
717
718 #[test]
719 fn box_field_has_pointer_size() {
720 let src = "struct S { inner: Box<u64> }";
721 let layouts = parse_rust(src, &X86_64_SYSV).unwrap();
722 assert_eq!(layouts[0].fields[0].size, 8);
723 }
724
725 #[test]
726 fn arc_field_has_pointer_size() {
727 let src = "struct S { shared: Arc<Vec<u8>> }";
728 let layouts = parse_rust(src, &X86_64_SYSV).unwrap();
729 assert_eq!(layouts[0].fields[0].size, 8);
730 }
731
732 #[test]
733 fn phantom_data_is_zero_sized() {
734 let src = "struct S { a: u64, _marker: PhantomData<u8> }";
735 let layouts = parse_rust(src, &X86_64_SYSV).unwrap();
736 let marker = layouts[0]
737 .fields
738 .iter()
739 .find(|f| f.name == "_marker")
740 .unwrap();
741 assert_eq!(marker.size, 0);
742 }
743
744 #[test]
745 fn duration_field_is_16_bytes() {
746 let src = "struct S { timeout: Duration }";
747 let layouts = parse_rust(src, &X86_64_SYSV).unwrap();
748 assert_eq!(layouts[0].fields[0].size, 16);
749 }
750
751 #[test]
752 fn atomic_u64_has_correct_size() {
753 let src = "struct S { counter: AtomicU64 }";
754 let layouts = parse_rust(src, &X86_64_SYSV).unwrap();
755 assert_eq!(layouts[0].fields[0].size, 8);
756 }
757
758 #[test]
759 fn atomic_bool_has_correct_size() {
760 let src = "struct S { flag: AtomicBool }";
761 let layouts = parse_rust(src, &X86_64_SYSV).unwrap();
762 assert_eq!(layouts[0].fields[0].size, 1);
763 }
764
765 #[test]
768 fn generic_struct_is_skipped() {
769 let src = "struct Wrapper<T> { value: T, count: usize }";
771 let layouts = parse_rust(src, &X86_64_SYSV).unwrap();
772 assert!(
773 layouts.is_empty(),
774 "generic structs should be skipped; got {:?}",
775 layouts.iter().map(|l| &l.name).collect::<Vec<_>>()
776 );
777 }
778
779 #[test]
780 fn generic_struct_with_multiple_params_is_skipped() {
781 let src = "struct Pair<A, B> { first: A, second: B }";
782 let layouts = parse_rust(src, &X86_64_SYSV).unwrap();
783 assert!(layouts.is_empty());
784 }
785
786 #[test]
787 fn non_generic_struct_still_parsed_when_generic_sibling_exists() {
788 let src = r#"
789struct Generic<T> { value: T }
790struct Concrete { a: u32, b: u64 }
791"#;
792 let layouts = parse_rust(src, &X86_64_SYSV).unwrap();
793 assert_eq!(layouts.len(), 1);
794 assert_eq!(layouts[0].name, "Concrete");
795 }
796
797 #[test]
800 fn unit_enum_is_just_discriminant() {
801 let src = "enum Color { Red, Green, Blue }";
802 let layouts = parse_rust(src, &X86_64_SYSV).unwrap();
803 assert_eq!(layouts.len(), 1);
804 let l = &layouts[0];
805 assert_eq!(l.name, "Color");
806 assert_eq!(l.total_size, 1); assert_eq!(l.fields.len(), 1);
808 assert_eq!(l.fields[0].name, "__discriminant");
809 }
810
811 #[test]
812 fn unit_enum_with_many_variants_uses_u16_discriminant() {
813 let variants: String = (0..300)
815 .map(|i| format!("V{i}"))
816 .collect::<Vec<_>>()
817 .join(", ");
818 let src = format!("enum Big {{ {variants} }}");
819 let layouts = parse_rust(&src, &X86_64_SYSV).unwrap();
820 let l = &layouts[0];
821 assert_eq!(l.total_size, 2); assert_eq!(l.fields[0].size, 2);
823 }
824
825 #[test]
826 fn data_enum_total_size_covers_largest_variant() {
827 let src = r#"
830enum Message {
831 Quit,
832 Move { x: i32, y: i32 },
833 Write(String),
834}
835"#;
836 let layouts = parse_rust(src, &X86_64_SYSV).unwrap();
837 let l = &layouts[0];
838 assert_eq!(l.name, "Message");
839 assert_eq!(l.total_size, 32);
841 assert_eq!(l.fields.len(), 2);
842 let payload = l.fields.iter().find(|f| f.name == "__payload").unwrap();
843 assert_eq!(payload.size, 24); }
845
846 #[test]
847 fn generic_enum_is_skipped() {
848 let src = "enum Wrapper<T> { Some(T), None }";
849 let layouts = parse_rust(src, &X86_64_SYSV).unwrap();
850 assert!(
851 layouts.is_empty(),
852 "generic enums should be skipped; got {:?}",
853 layouts.iter().map(|l| &l.name).collect::<Vec<_>>()
854 );
855 }
856
857 #[test]
858 fn empty_enum_is_skipped() {
859 let src = "enum Never {}";
860 let layouts = parse_rust(src, &X86_64_SYSV).unwrap();
861 assert!(layouts.is_empty());
862 }
863
864 #[test]
865 fn enum_with_only_unit_variants_has_no_payload_field() {
866 let src = "enum Dir { North, South, East, West }";
867 let layouts = parse_rust(src, &X86_64_SYSV).unwrap();
868 assert!(!layouts[0].fields.iter().any(|f| f.name == "__payload"));
869 }
870
871 #[test]
872 fn data_enum_and_sibling_struct_both_parsed() {
873 let src = r#"
874enum Status { Ok, Err(u32) }
875struct Conn { port: u16, status: u32 }
876"#;
877 let layouts = parse_rust(src, &X86_64_SYSV).unwrap();
878 assert_eq!(layouts.len(), 2);
879 assert!(layouts.iter().any(|l| l.name == "Status"));
880 assert!(layouts.iter().any(|l| l.name == "Conn"));
881 }
882
883 #[test]
886 fn enum_with_only_zero_sized_variants_has_payload_size_zero() {
887 let src = "enum E { A, B }";
889 let layouts = parse_rust(src, &X86_64_SYSV).unwrap();
890 let l = &layouts[0];
891 assert_eq!(l.total_size, 1);
892 }
893
894 #[test]
895 fn enum_mixed_unit_and_data_includes_max_payload() {
896 let src = "enum E { Nothing, Data(u64) }";
898 let layouts = parse_rust(src, &X86_64_SYSV).unwrap();
899 let l = &layouts[0];
900 let payload = l.fields.iter().find(|f| f.name == "__payload").unwrap();
901 assert_eq!(payload.size, 8); }
903
904 #[test]
907 fn repr_align_raises_struct_alignment() {
908 let src = "#[repr(align(64))]\nstruct CacheLine { a: u8, b: u32 }";
909 let layouts = parse_rust(src, &X86_64_SYSV).unwrap();
910 let l = &layouts[0];
911 assert_eq!(
912 l.align, 64,
913 "repr(align(64)) must set struct alignment to 64"
914 );
915 assert_eq!(l.total_size, 64, "size must be padded to 64 bytes");
916 }
917
918 #[test]
919 fn repr_align_does_not_shrink_natural_alignment() {
920 let src = "#[repr(align(1))]\nstruct S { a: u64 }";
922 let layouts = parse_rust(src, &X86_64_SYSV).unwrap();
923 let l = &layouts[0];
924 assert_eq!(
925 l.align, 8,
926 "natural align must not be reduced below repr(align)"
927 );
928 }
929
930 #[test]
931 fn repr_align_adds_trailing_padding() {
932 let src = "#[repr(align(8))]\nstruct S { a: u8, b: u32 }";
934 let layouts = parse_rust(src, &X86_64_SYSV).unwrap();
935 let l = &layouts[0];
936 assert_eq!(l.total_size, 8);
937 }
938
939 #[test]
940 fn no_repr_align_has_natural_size() {
941 let src = "struct S { a: u8, b: u32 }";
943 let layouts = parse_rust(src, &X86_64_SYSV).unwrap();
944 let l = &layouts[0];
945 assert_eq!(l.total_size, 8);
947 assert_eq!(l.align, 4);
948 }
949
950 #[test]
953 fn tuple_struct_fields_named_by_index() {
954 let src = "struct Pair(u64, u8);";
955 let layouts = parse_rust(src, &X86_64_SYSV).unwrap();
956 let l = &layouts[0];
957 assert_eq!(l.fields[0].name, "_0");
958 assert_eq!(l.fields[1].name, "_1");
959 }
960
961 #[test]
962 fn tuple_struct_layout_follows_alignment() {
963 let src = "struct S(u64, u8);";
965 let layouts = parse_rust(src, &X86_64_SYSV).unwrap();
966 let l = &layouts[0];
967 assert_eq!(l.fields[0].offset, 0);
968 assert_eq!(l.fields[0].size, 8);
969 assert_eq!(l.fields[1].offset, 8);
970 assert_eq!(l.fields[1].size, 1);
971 assert_eq!(l.total_size, 16);
972 }
973
974 #[test]
975 fn tuple_struct_with_padding_waste_detected() {
976 let src = "struct S(u8, u64);";
978 let layouts = parse_rust(src, &X86_64_SYSV).unwrap();
979 let l = &layouts[0];
980 assert_eq!(l.fields[0].offset, 0); assert_eq!(l.fields[1].offset, 8); assert_eq!(l.total_size, 16);
983 let gaps = padlock_core::ir::find_padding(l);
984 assert_eq!(gaps[0].bytes, 7);
985 }
986
987 #[test]
990 fn nonzero_types_same_size_as_base() {
991 assert_eq!(primitive_size_align("NonZeroU8", &X86_64_SYSV), (1, 1));
992 assert_eq!(primitive_size_align("NonZeroI8", &X86_64_SYSV), (1, 1));
993 assert_eq!(primitive_size_align("NonZeroU16", &X86_64_SYSV), (2, 2));
994 assert_eq!(primitive_size_align("NonZeroU32", &X86_64_SYSV), (4, 4));
995 assert_eq!(primitive_size_align("NonZeroU64", &X86_64_SYSV), (8, 8));
996 assert_eq!(primitive_size_align("NonZeroU128", &X86_64_SYSV), (16, 16));
997 assert_eq!(
998 primitive_size_align("NonZeroUsize", &X86_64_SYSV),
999 (X86_64_SYSV.pointer_size, X86_64_SYSV.pointer_size)
1000 );
1001 }
1002
1003 #[test]
1004 fn float16_and_float128_correct_size() {
1005 assert_eq!(primitive_size_align("f16", &X86_64_SYSV), (2, 2));
1006 assert_eq!(primitive_size_align("f128", &X86_64_SYSV), (16, 16));
1007 }
1008
1009 #[test]
1010 fn rust_struct_with_nonzero_fields() {
1011 let src = "struct Counts { hits: NonZeroU64, misses: NonZeroU32, flags: u8 }";
1012 let layouts = parse_rust(src, &X86_64_SYSV).unwrap();
1013 let l = &layouts[0];
1014 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);
1019 }
1020
1021 #[test]
1024 fn plain_struct_is_repr_rust() {
1025 let src = "struct Foo { a: u64, b: u32 }";
1026 let layouts = parse_rust(src, &X86_64_SYSV).unwrap();
1027 assert!(layouts[0].is_repr_rust, "plain struct should be repr(Rust)");
1028 }
1029
1030 #[test]
1031 fn repr_c_struct_is_not_repr_rust() {
1032 let src = "#[repr(C)] struct Foo { a: u64, b: u32 }";
1033 let layouts = parse_rust(src, &X86_64_SYSV).unwrap();
1034 assert!(
1035 !layouts[0].is_repr_rust,
1036 "repr(C) struct must not be repr(Rust)"
1037 );
1038 }
1039
1040 #[test]
1041 fn repr_packed_struct_is_not_repr_rust() {
1042 let src = "#[repr(packed)] struct Foo { a: u64, b: u32 }";
1043 let layouts = parse_rust(src, &X86_64_SYSV).unwrap();
1044 assert!(
1045 !layouts[0].is_repr_rust,
1046 "repr(packed) struct must not be repr(Rust)"
1047 );
1048 }
1049
1050 #[test]
1051 fn repr_transparent_struct_is_not_repr_rust() {
1052 let src = "#[repr(transparent)] struct Wrapper(u64);";
1053 let layouts = parse_rust(src, &X86_64_SYSV).unwrap();
1054 assert!(
1055 !layouts[0].is_repr_rust,
1056 "repr(transparent) struct must not be repr(Rust)"
1057 );
1058 }
1059
1060 #[test]
1061 fn plain_enum_is_repr_rust() {
1062 let src = "enum Color { Red, Green, Blue }";
1063 let layouts = parse_rust(src, &X86_64_SYSV).unwrap();
1064 assert!(layouts[0].is_repr_rust, "plain enum should be repr(Rust)");
1065 }
1066
1067 #[test]
1068 fn repr_c_enum_is_not_repr_rust() {
1069 let src = "#[repr(C)] enum Dir { North, South }";
1070 let layouts = parse_rust(src, &X86_64_SYSV).unwrap();
1071 assert!(
1072 !layouts[0].is_repr_rust,
1073 "repr(C) enum must not be repr(Rust)"
1074 );
1075 }
1076}