1use padlock_core::arch::ArchConfig;
8use padlock_core::ir::{AccessPattern, Field, StructLayout, TypeInfo};
9use tree_sitter::{Node, Parser};
10
11fn c_type_size_align(ty: &str, arch: &'static ArchConfig) -> (usize, usize) {
15 let ty = ty.trim();
16 for qual in &["const ", "volatile ", "restrict ", "unsigned ", "signed "] {
18 if let Some(rest) = ty.strip_prefix(qual) {
19 return c_type_size_align(rest, arch);
20 }
21 }
22 match ty {
24 "__m64" => return (8, 8),
25 "__m128" | "__m128d" | "__m128i" => return (16, 16),
26 "__m256" | "__m256d" | "__m256i" => return (32, 32),
27 "__m512" | "__m512d" | "__m512i" => return (64, 64),
28 "float32x2_t" | "int32x2_t" | "uint32x2_t" | "int8x8_t" | "uint8x8_t" | "int16x4_t"
30 | "uint16x4_t" | "float64x1_t" | "int64x1_t" | "uint64x1_t" => return (8, 8),
31 "float32x4_t" | "int32x4_t" | "uint32x4_t" | "float64x2_t" | "int64x2_t" | "uint64x2_t"
33 | "int8x16_t" | "uint8x16_t" | "int16x8_t" | "uint16x8_t" => return (16, 16),
34 _ => {}
35 }
36 match ty {
40 "std::mutex"
42 | "std::recursive_mutex"
43 | "std::timed_mutex"
44 | "std::recursive_timed_mutex"
45 | "pthread_mutex_t" => return (40, 8),
46 "std::shared_mutex" | "std::shared_timed_mutex" => return (56, 8),
47 "std::condition_variable" | "pthread_cond_t" => return (48, 8),
49 ty if ty.starts_with("std::atomic<") && ty.ends_with('>') => {
51 let inner = &ty[12..ty.len() - 1];
52 return c_type_size_align(inner.trim(), arch);
53 }
54 _ => {} }
56 match ty {
58 "char" | "_Bool" | "bool" => (1, 1),
59 "short" | "short int" => (2, 2),
60 "int" => (4, 4),
61 "long" => (arch.pointer_size, arch.pointer_size),
62 "long long" => (8, 8),
63 "float" => (4, 4),
64 "double" => (8, 8),
65 "long double" => (16, 16),
66 "int8_t" | "uint8_t" => (1, 1),
67 "int16_t" | "uint16_t" => (2, 2),
68 "int32_t" | "uint32_t" => (4, 4),
69 "int64_t" | "uint64_t" => (8, 8),
70 "size_t" | "ssize_t" | "ptrdiff_t" | "intptr_t" | "uintptr_t" => {
71 (arch.pointer_size, arch.pointer_size)
72 }
73 ty if ty.ends_with('*') => (arch.pointer_size, arch.pointer_size),
75 _ => (arch.pointer_size, arch.pointer_size),
77 }
78}
79
80fn strip_bitfield_suffix(ty: &str) -> &str {
85 if let Some(pos) = ty.rfind(':') {
86 let suffix = ty[pos + 1..].trim();
87 if !suffix.is_empty() && suffix.bytes().all(|b| b.is_ascii_digit()) {
88 return ty[..pos].trim_end();
89 }
90 }
91 ty
92}
93
94fn simulate_layout(
96 fields: &mut Vec<Field>,
97 struct_name: String,
98 arch: &'static ArchConfig,
99) -> StructLayout {
100 let mut offset = 0usize;
101 let mut struct_align = 1usize;
102
103 for f in fields.iter_mut() {
104 if f.align > 0 {
105 offset = offset.next_multiple_of(f.align);
106 }
107 f.offset = offset;
108 offset += f.size;
109 struct_align = struct_align.max(f.align);
110 }
111 if struct_align > 0 {
113 offset = offset.next_multiple_of(struct_align);
114 }
115
116 StructLayout {
117 name: struct_name,
118 total_size: offset,
119 align: struct_align,
120 fields: fields.drain(..).collect(),
121 source_file: None,
122 source_line: None,
123 arch,
124 is_packed: false,
125 is_union: false,
126 }
127}
128
129fn simulate_union_layout(
132 fields: &mut Vec<Field>,
133 name: String,
134 arch: &'static ArchConfig,
135) -> StructLayout {
136 for f in fields.iter_mut() {
137 f.offset = 0;
138 }
139 let max_size = fields.iter().map(|f| f.size).max().unwrap_or(0);
140 let max_align = fields.iter().map(|f| f.align).max().unwrap_or(1);
141 let total_size = if max_align > 0 {
142 max_size.next_multiple_of(max_align)
143 } else {
144 max_size
145 };
146
147 StructLayout {
148 name,
149 total_size,
150 align: max_align,
151 fields: fields.drain(..).collect(),
152 source_file: None,
153 source_line: None,
154 arch,
155 is_packed: false,
156 is_union: true,
157 }
158}
159
160fn parse_class_specifier(
167 source: &str,
168 node: Node<'_>,
169 arch: &'static ArchConfig,
170) -> Option<StructLayout> {
171 let mut class_name = "<anonymous>".to_string();
172 let mut base_names: Vec<String> = Vec::new();
173 let mut body_node: Option<Node> = None;
174
175 for i in 0..node.child_count() {
176 let child = node.child(i)?;
177 match child.kind() {
178 "type_identifier" => class_name = source[child.byte_range()].to_string(),
179 "base_class_clause" => {
180 for j in 0..child.child_count() {
183 if let Some(base) = child.child(j) {
184 if base.kind() == "type_identifier" {
185 base_names.push(source[base.byte_range()].to_string());
186 }
187 }
188 }
189 }
190 "field_declaration_list" => body_node = Some(child),
191 _ => {}
192 }
193 }
194
195 let body = body_node?;
196
197 let has_virtual = contains_virtual_keyword(source, body);
199
200 let mut raw_fields: Vec<(String, String, Option<String>)> = Vec::new();
202 for i in 0..body.child_count() {
203 if let Some(child) = body.child(i) {
204 if child.kind() == "field_declaration" {
205 if let Some((ty, fname, guard)) = parse_field_declaration(source, child) {
206 raw_fields.push((fname, ty, guard));
207 }
208 }
209 }
210 }
211
212 let mut fields: Vec<Field> = Vec::new();
214
215 if has_virtual {
217 let ps = arch.pointer_size;
218 fields.push(Field {
219 name: "__vptr".to_string(),
220 ty: TypeInfo::Pointer { size: ps, align: ps },
221 offset: 0,
222 size: ps,
223 align: ps,
224 source_file: None,
225 source_line: None,
226 access: AccessPattern::Unknown,
227 });
228 }
229
230 for base in &base_names {
232 let ps = arch.pointer_size;
233 fields.push(Field {
234 name: format!("__base_{base}"),
235 ty: TypeInfo::Opaque {
236 name: base.clone(),
237 size: ps,
238 align: ps,
239 },
240 offset: 0,
241 size: ps,
242 align: ps,
243 source_file: None,
244 source_line: None,
245 access: AccessPattern::Unknown,
246 });
247 }
248
249 for (fname, ty_name, guard) in raw_fields {
251 let base_ty = strip_bitfield_suffix(&ty_name);
252 let (size, align) = c_type_size_align(base_ty, arch);
253 let access = if let Some(g) = guard {
254 AccessPattern::Concurrent { guard: Some(g), is_atomic: false }
255 } else {
256 AccessPattern::Unknown
257 };
258 fields.push(Field {
259 name: fname,
260 ty: TypeInfo::Primitive { name: ty_name, size, align },
261 offset: 0,
262 size,
263 align,
264 source_file: None,
265 source_line: None,
266 access,
267 });
268 }
269
270 if fields.is_empty() {
271 return None;
272 }
273
274 Some(simulate_layout(&mut fields, class_name, arch))
275}
276
277fn contains_virtual_keyword(source: &str, node: Node<'_>) -> bool {
280 let mut stack = vec![node];
281 while let Some(n) = stack.pop() {
282 if n.kind() == "virtual" {
283 return true;
284 }
285 if n.child_count() == 0 {
288 let text = &source[n.byte_range()];
289 if text == "virtual" {
290 return true;
291 }
292 }
293 for i in (0..n.child_count()).rev() {
294 if let Some(child) = n.child(i) {
295 stack.push(child);
296 }
297 }
298 }
299 false
300}
301
302fn extract_structs_from_tree(
305 source: &str,
306 root: Node<'_>,
307 arch: &'static ArchConfig,
308 layouts: &mut Vec<StructLayout>,
309) {
310 let cursor = root.walk();
311 let mut stack = vec![root];
312
313 while let Some(node) = stack.pop() {
314 for i in (0..node.child_count()).rev() {
316 if let Some(child) = node.child(i) {
317 stack.push(child);
318 }
319 }
320
321 match node.kind() {
322 "struct_specifier" => {
323 if let Some(layout) = parse_struct_or_union_specifier(source, node, arch, false) {
324 layouts.push(layout);
325 }
326 }
327 "union_specifier" => {
328 if let Some(layout) = parse_struct_or_union_specifier(source, node, arch, true) {
329 layouts.push(layout);
330 }
331 }
332 "class_specifier" => {
333 if let Some(layout) = parse_class_specifier(source, node, arch) {
334 layouts.push(layout);
335 }
336 }
337 _ => {}
338 }
339 }
340
341 let cursor2 = root.walk();
343 let mut stack2 = vec![root];
344 while let Some(node) = stack2.pop() {
345 for i in (0..node.child_count()).rev() {
346 if let Some(child) = node.child(i) {
347 stack2.push(child);
348 }
349 }
350 if node.kind() == "type_definition" {
351 if let Some(layout) = parse_typedef_struct_or_union(source, node, arch) {
352 let existing = layouts
353 .iter()
354 .position(|l| l.name == layout.name || l.name == "<anonymous>");
355 match existing {
356 Some(i) if layouts[i].name == "<anonymous>" => {
357 layouts[i] = layout;
358 }
359 None => layouts.push(layout),
360 _ => {}
361 }
362 }
363 }
364 }
365 let _ = cursor;
366 let _ = cursor2; }
368
369fn parse_struct_or_union_specifier(
371 source: &str,
372 node: Node<'_>,
373 arch: &'static ArchConfig,
374 is_union: bool,
375) -> Option<StructLayout> {
376 let mut name = "<anonymous>".to_string();
377 let mut body_node: Option<Node> = None;
378
379 for i in 0..node.child_count() {
380 let child = node.child(i)?;
381 match child.kind() {
382 "type_identifier" => name = source[child.byte_range()].to_string(),
383 "field_declaration_list" => body_node = Some(child),
384 _ => {}
385 }
386 }
387
388 let body = body_node?;
389 let mut raw_fields: Vec<(String, String, Option<String>)> = Vec::new();
390
391 for i in 0..body.child_count() {
392 let child = body.child(i)?;
393 if child.kind() == "field_declaration" {
394 if let Some((ty, fname, guard)) = parse_field_declaration(source, child) {
395 raw_fields.push((fname, ty, guard));
396 }
397 }
398 }
399
400 if raw_fields.is_empty() {
401 return None;
402 }
403
404 let mut fields: Vec<Field> = raw_fields
405 .into_iter()
406 .map(|(fname, ty_name, guard)| {
407 let base = strip_bitfield_suffix(&ty_name);
409 let (size, align) = c_type_size_align(base, arch);
410 let access = if let Some(g) = guard {
411 AccessPattern::Concurrent {
412 guard: Some(g),
413 is_atomic: false,
414 }
415 } else {
416 AccessPattern::Unknown
417 };
418 Field {
419 name: fname,
420 ty: TypeInfo::Primitive {
421 name: ty_name,
422 size,
423 align,
424 },
425 offset: 0,
426 size,
427 align,
428 source_file: None,
429 source_line: None,
430 access,
431 }
432 })
433 .collect();
434
435 if is_union {
436 Some(simulate_union_layout(&mut fields, name, arch))
437 } else {
438 Some(simulate_layout(&mut fields, name, arch))
439 }
440}
441
442fn parse_typedef_struct_or_union(
444 source: &str,
445 node: Node<'_>,
446 arch: &'static ArchConfig,
447) -> Option<StructLayout> {
448 let mut specifier_node: Option<Node> = None;
449 let mut is_union = false;
450 let mut typedef_name: Option<String> = None;
451
452 for i in 0..node.child_count() {
453 let child = node.child(i)?;
454 match child.kind() {
455 "struct_specifier" => {
456 specifier_node = Some(child);
457 is_union = false;
458 }
459 "union_specifier" => {
460 specifier_node = Some(child);
461 is_union = true;
462 }
463 "type_identifier" => typedef_name = Some(source[child.byte_range()].to_string()),
464 _ => {}
465 }
466 }
467
468 let spec = specifier_node?;
469 let typedef_name = typedef_name?;
470
471 let mut layout = parse_struct_or_union_specifier(source, spec, arch, is_union)?;
472 if layout.name == "<anonymous>" {
473 layout.name = typedef_name;
474 }
475 Some(layout)
476}
477
478#[allow(dead_code)]
480fn parse_typedef_struct(
481 source: &str,
482 node: Node<'_>,
483 arch: &'static ArchConfig,
484) -> Option<StructLayout> {
485 parse_typedef_struct_or_union(source, node, arch)
486}
487
488fn extract_guard_from_c_field_text(field_source: &str) -> Option<String> {
496 for kw in &["guarded_by", "pt_guarded_by", "GUARDED_BY", "PT_GUARDED_BY"] {
498 if let Some(pos) = field_source.find(kw) {
499 let after = &field_source[pos + kw.len()..];
500 let trimmed = after.trim_start();
502 if trimmed.starts_with('(') {
503 let inner = &trimmed[1..];
504 if let Some(end) = inner.find(')') {
506 let guard = inner[..end].trim().trim_matches('"');
507 if !guard.is_empty() {
508 return Some(guard.to_string());
509 }
510 }
511 }
512 }
513 }
514 None
515}
516
517fn parse_field_declaration(
518 source: &str,
519 node: Node<'_>,
520) -> Option<(String, String, Option<String>)> {
521 let mut ty_parts: Vec<String> = Vec::new();
522 let mut field_name: Option<String> = None;
523 let mut bit_width: Option<String> = None;
525 let mut attr_text = String::new();
527
528 for i in 0..node.child_count() {
529 let child = node.child(i)?;
530 match child.kind() {
531 "type_specifier" | "primitive_type" | "type_identifier" | "sized_type_specifier" => {
532 ty_parts.push(source[child.byte_range()].trim().to_string());
533 }
534 "qualified_identifier" | "template_type" => {
537 ty_parts.push(source[child.byte_range()].trim().to_string());
538 }
539 "struct_specifier" | "union_specifier" => {
543 for j in 0..child.child_count() {
544 if let Some(sub) = child.child(j) {
545 if sub.kind() == "type_identifier" {
546 ty_parts.push(source[sub.byte_range()].trim().to_string());
547 break;
548 }
549 }
550 }
551 }
552 "field_identifier" => {
553 field_name = Some(source[child.byte_range()].trim().to_string());
554 }
555 "pointer_declarator" => {
556 field_name = extract_identifier(source, child);
557 ty_parts.push("*".to_string());
558 }
559 "bitfield_clause" => {
561 let text = source[child.byte_range()].trim();
562 bit_width = Some(text.trim_start_matches(':').trim().to_string());
564 }
565 "attribute_specifier" | "attribute" => {
567 attr_text.push_str(source[child.byte_range()].trim());
568 attr_text.push(' ');
569 }
570 _ => {}
571 }
572 }
573
574 let base_ty = ty_parts.join(" ");
575 let fname = field_name?;
576 if base_ty.is_empty() {
577 return None;
578 }
579 let ty = if let Some(w) = bit_width {
582 format!("{base_ty}:{w}")
583 } else {
584 base_ty
585 };
586
587 let field_src = source[node.byte_range()].to_string();
590 let guard = extract_guard_from_c_field_text(&attr_text)
591 .or_else(|| extract_guard_from_c_field_text(&field_src));
592
593 Some((ty, fname, guard))
594}
595
596fn extract_identifier(source: &str, node: Node<'_>) -> Option<String> {
597 if node.kind() == "field_identifier" || node.kind() == "identifier" {
598 return Some(source[node.byte_range()].to_string());
599 }
600 for i in 0..node.child_count() {
601 if let Some(child) = node.child(i) {
602 if let Some(name) = extract_identifier(source, child) {
603 return Some(name);
604 }
605 }
606 }
607 None
608}
609
610pub fn parse_c(source: &str, arch: &'static ArchConfig) -> anyhow::Result<Vec<StructLayout>> {
613 let mut parser = Parser::new();
614 parser.set_language(&tree_sitter_c::language())?;
615 let tree = parser
616 .parse(source, None)
617 .ok_or_else(|| anyhow::anyhow!("tree-sitter parse failed"))?;
618 let mut layouts = Vec::new();
619 extract_structs_from_tree(source, tree.root_node(), arch, &mut layouts);
620 Ok(layouts)
621}
622
623pub fn parse_cpp(source: &str, arch: &'static ArchConfig) -> anyhow::Result<Vec<StructLayout>> {
624 let mut parser = Parser::new();
625 parser.set_language(&tree_sitter_cpp::language())?;
626 let tree = parser
627 .parse(source, None)
628 .ok_or_else(|| anyhow::anyhow!("tree-sitter parse failed"))?;
629 let mut layouts = Vec::new();
630 extract_structs_from_tree(source, tree.root_node(), arch, &mut layouts);
631 Ok(layouts)
632}
633
634#[cfg(test)]
637mod tests {
638 use super::*;
639 use padlock_core::arch::X86_64_SYSV;
640
641 #[test]
642 fn parse_simple_c_struct() {
643 let src = r#"
644struct Point {
645 int x;
646 int y;
647};
648"#;
649 let layouts = parse_c(src, &X86_64_SYSV).unwrap();
650 assert_eq!(layouts.len(), 1);
651 assert_eq!(layouts[0].name, "Point");
652 assert_eq!(layouts[0].fields.len(), 2);
653 assert_eq!(layouts[0].fields[0].name, "x");
654 assert_eq!(layouts[0].fields[1].name, "y");
655 }
656
657 #[test]
658 fn parse_typedef_struct() {
659 let src = r#"
660typedef struct {
661 char is_active;
662 double timeout;
663 int port;
664} Connection;
665"#;
666 let layouts = parse_c(src, &X86_64_SYSV).unwrap();
667 assert_eq!(layouts.len(), 1);
668 assert_eq!(layouts[0].name, "Connection");
669 assert_eq!(layouts[0].fields.len(), 3);
670 }
671
672 #[test]
673 fn c_layout_computes_offsets() {
674 let src = "struct T { char a; double b; };";
675 let layouts = parse_c(src, &X86_64_SYSV).unwrap();
676 assert_eq!(layouts.len(), 1);
677 let layout = &layouts[0];
678 assert_eq!(layout.fields[0].offset, 0);
680 assert_eq!(layout.fields[1].offset, 8);
681 assert_eq!(layout.total_size, 16);
682 }
683
684 #[test]
685 fn c_layout_detects_padding() {
686 let src = "struct T { char a; int b; };";
687 let layouts = parse_c(src, &X86_64_SYSV).unwrap();
688 let gaps = padlock_core::ir::find_padding(&layouts[0]);
689 assert!(!gaps.is_empty());
690 assert_eq!(gaps[0].bytes, 3); }
692
693 #[test]
694 fn parse_cpp_struct() {
695 let src = "struct Vec3 { float x; float y; float z; };";
696 let layouts = parse_cpp(src, &X86_64_SYSV).unwrap();
697 assert_eq!(layouts.len(), 1);
698 assert_eq!(layouts[0].fields.len(), 3);
699 }
700
701 #[test]
704 fn simd_sse_field_size_and_align() {
705 let src = "struct Vecs { __m128 a; __m256 b; };";
706 let layouts = parse_c(src, &X86_64_SYSV).unwrap();
707 assert_eq!(layouts.len(), 1);
708 let f = &layouts[0].fields;
709 assert_eq!(f[0].size, 16); assert_eq!(f[0].align, 16);
711 assert_eq!(f[1].size, 32); assert_eq!(f[1].align, 32);
713 }
714
715 #[test]
716 fn simd_avx512_size() {
717 let src = "struct Wide { __m512 v; };";
718 let layouts = parse_c(src, &X86_64_SYSV).unwrap();
719 assert_eq!(layouts[0].fields[0].size, 64);
720 assert_eq!(layouts[0].fields[0].align, 64);
721 }
722
723 #[test]
724 fn simd_padding_detected_when_small_field_before_avx() {
725 let src = "struct Mixed { char flag; __m256 data; };";
727 let layouts = parse_c(src, &X86_64_SYSV).unwrap();
728 let gaps = padlock_core::ir::find_padding(&layouts[0]);
729 assert!(!gaps.is_empty());
730 assert_eq!(gaps[0].bytes, 31);
731 }
732
733 #[test]
736 fn union_fields_all_at_offset_zero() {
737 let src = "union Data { int i; float f; double d; };";
738 let layouts = parse_c(src, &X86_64_SYSV).unwrap();
739 assert_eq!(layouts.len(), 1);
740 let u = &layouts[0];
741 assert!(u.is_union);
742 for field in &u.fields {
743 assert_eq!(
744 field.offset, 0,
745 "union field '{}' should be at offset 0",
746 field.name
747 );
748 }
749 }
750
751 #[test]
752 fn union_total_size_is_max_field() {
753 let src = "union Data { int i; float f; double d; };";
755 let layouts = parse_c(src, &X86_64_SYSV).unwrap();
756 assert_eq!(layouts[0].total_size, 8);
757 }
758
759 #[test]
760 fn union_no_padding_finding() {
761 let src = "union Data { int i; double d; };";
762 let layouts = parse_c(src, &X86_64_SYSV).unwrap();
763 let report = padlock_core::findings::Report::from_layouts(&layouts);
764 let sr = &report.structs[0];
765 assert!(!sr
766 .findings
767 .iter()
768 .any(|f| matches!(f, padlock_core::findings::Finding::PaddingWaste { .. })));
769 assert!(!sr
770 .findings
771 .iter()
772 .any(|f| matches!(f, padlock_core::findings::Finding::ReorderSuggestion { .. })));
773 }
774
775 #[test]
776 fn typedef_union_parsed() {
777 let src = "typedef union { int a; double b; } Value;";
778 let layouts = parse_c(src, &X86_64_SYSV).unwrap();
779 assert_eq!(layouts.len(), 1);
780 assert_eq!(layouts[0].name, "Value");
781 assert!(layouts[0].is_union);
782 }
783
784 #[test]
787 fn bitfield_type_annotated_with_width() {
788 let src = "struct Flags { int a : 3; int b : 5; };";
789 let layouts = parse_c(src, &X86_64_SYSV).unwrap();
790 assert_eq!(layouts.len(), 1);
791 let names: Vec<&str> = layouts[0].fields.iter().map(|f| f.name.as_str()).collect();
793 assert!(names.contains(&"a") && names.contains(&"b"));
794 let a_ty = match &layouts[0].fields[0].ty {
796 padlock_core::ir::TypeInfo::Primitive { name, .. } => name.clone(),
797 _ => panic!("expected Primitive"),
798 };
799 assert!(
800 a_ty.contains(':'),
801 "bit field type should contain ':' width annotation"
802 );
803 }
804
805 #[test]
806 fn bitfield_uses_storage_unit_size() {
807 let src = "struct S { int a : 3; };";
809 let layouts = parse_c(src, &X86_64_SYSV).unwrap();
810 assert_eq!(layouts[0].fields[0].size, 4);
811 }
812
813 #[test]
816 fn extract_guard_from_c_guarded_by_macro() {
817 let text = "int value GUARDED_BY(mu);";
818 let guard = extract_guard_from_c_field_text(text);
819 assert_eq!(guard.as_deref(), Some("mu"));
820 }
821
822 #[test]
823 fn extract_guard_from_c_attribute_specifier() {
824 let text = "__attribute__((guarded_by(counter_lock))) uint64_t counter;";
825 let guard = extract_guard_from_c_field_text(text);
826 assert_eq!(guard.as_deref(), Some("counter_lock"));
827 }
828
829 #[test]
830 fn extract_guard_pt_guarded_by() {
831 let text = "int *ptr PT_GUARDED_BY(ptr_lock);";
832 let guard = extract_guard_from_c_field_text(text);
833 assert_eq!(guard.as_deref(), Some("ptr_lock"));
834 }
835
836 #[test]
837 fn no_guard_returns_none() {
838 let guard = extract_guard_from_c_field_text("int x;");
839 assert!(guard.is_none());
840 }
841
842 #[test]
843 fn c_struct_guarded_by_sets_concurrent_access() {
844 let text = "uint64_t readers GUARDED_BY(lock_a);";
848 assert_eq!(
849 extract_guard_from_c_field_text(text).as_deref(),
850 Some("lock_a")
851 );
852 }
853
854 #[test]
855 fn c_struct_different_guards_detected_as_false_sharing() {
856 use padlock_core::arch::X86_64_SYSV;
857 use padlock_core::ir::{AccessPattern, Field, StructLayout, TypeInfo};
858
859 let mut layout = StructLayout {
863 name: "S".into(),
864 total_size: 128,
865 align: 8,
866 fields: vec![
867 Field {
868 name: "readers".into(),
869 ty: TypeInfo::Primitive {
870 name: "uint64_t".into(),
871 size: 8,
872 align: 8,
873 },
874 offset: 0,
875 size: 8,
876 align: 8,
877 source_file: None,
878 source_line: None,
879 access: AccessPattern::Concurrent {
880 guard: Some("lock_a".into()),
881 is_atomic: false,
882 },
883 },
884 Field {
885 name: "writers".into(),
886 ty: TypeInfo::Primitive {
887 name: "uint64_t".into(),
888 size: 8,
889 align: 8,
890 },
891 offset: 8,
892 size: 8,
893 align: 8,
894 source_file: None,
895 source_line: None,
896 access: AccessPattern::Concurrent {
897 guard: Some("lock_b".into()),
898 is_atomic: false,
899 },
900 },
901 ],
902 source_file: None,
903 source_line: None,
904 arch: &X86_64_SYSV,
905 is_packed: false,
906 is_union: false,
907 };
908 assert!(padlock_core::analysis::false_sharing::has_false_sharing(
909 &layout
910 ));
911 layout.fields[1].access = AccessPattern::Concurrent {
913 guard: Some("lock_a".into()),
914 is_atomic: false,
915 };
916 assert!(!padlock_core::analysis::false_sharing::has_false_sharing(
917 &layout
918 ));
919 }
920
921 #[test]
924 fn cpp_class_with_virtual_method_has_vptr() {
925 let src = r#"
926class Widget {
927 virtual void draw();
928 int x;
929 int y;
930};
931"#;
932 let layouts = parse_cpp(src, &X86_64_SYSV).unwrap();
933 assert_eq!(layouts.len(), 1);
934 let l = &layouts[0];
935 assert_eq!(l.fields[0].name, "__vptr");
937 assert_eq!(l.fields[0].size, 8); assert_eq!(l.fields[0].offset, 0);
940 let x = l.fields.iter().find(|f| f.name == "x").unwrap();
942 assert_eq!(x.offset, 8);
943 }
944
945 #[test]
946 fn cpp_class_without_virtual_has_no_vptr() {
947 let src = "class Plain { int a; int b; };";
948 let layouts = parse_cpp(src, &X86_64_SYSV).unwrap();
949 assert_eq!(layouts.len(), 1);
950 assert!(!layouts[0].fields.iter().any(|f| f.name == "__vptr"));
951 }
952
953 #[test]
954 fn cpp_struct_keyword_with_virtual_has_vptr() {
955 let src = "struct IFoo { virtual ~IFoo(); virtual void bar(); };";
957 let layouts = parse_cpp(src, &X86_64_SYSV).unwrap();
958 let _ = layouts; }
962
963 #[test]
966 fn cpp_derived_class_has_base_slot() {
967 let src = r#"
968class Base {
969 int x;
970};
971class Derived : public Base {
972 int y;
973};
974"#;
975 let layouts = parse_cpp(src, &X86_64_SYSV).unwrap();
976 let derived = layouts.iter().find(|l| l.name == "Derived").unwrap();
978 assert!(
980 derived.fields.iter().any(|f| f.name == "__base_Base"),
981 "Derived should have a __base_Base field"
982 );
983 let base_field = derived.fields.iter().find(|f| f.name == "__base_Base").unwrap();
985 let y_field = derived.fields.iter().find(|f| f.name == "y").unwrap();
986 assert!(y_field.offset >= base_field.offset + base_field.size);
987 }
988
989 #[test]
990 fn cpp_class_multiple_inheritance_has_multiple_base_slots() {
991 let src = r#"
992class A { int a; };
993class B { int b; };
994class C : public A, public B { int c; };
995"#;
996 let layouts = parse_cpp(src, &X86_64_SYSV).unwrap();
997 let c = layouts.iter().find(|l| l.name == "C").unwrap();
998 assert!(c.fields.iter().any(|f| f.name == "__base_A"));
999 assert!(c.fields.iter().any(|f| f.name == "__base_B"));
1000 }
1001
1002 #[test]
1003 fn cpp_virtual_base_class_total_size_accounts_for_vptr() {
1004 let src = "class V { virtual void f(); int x; };";
1006 let layouts = parse_cpp(src, &X86_64_SYSV).unwrap();
1007 let l = &layouts[0];
1008 assert_eq!(l.total_size, 16);
1010 }
1011}