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