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 source_line: Option<u32>,
100) -> StructLayout {
101 let mut offset = 0usize;
102 let mut struct_align = 1usize;
103
104 for f in fields.iter_mut() {
105 if f.align > 0 {
106 offset = offset.next_multiple_of(f.align);
107 }
108 f.offset = offset;
109 offset += f.size;
110 struct_align = struct_align.max(f.align);
111 }
112 if struct_align > 0 {
114 offset = offset.next_multiple_of(struct_align);
115 }
116
117 StructLayout {
118 name: struct_name,
119 total_size: offset,
120 align: struct_align,
121 fields: std::mem::take(fields),
122 source_file: None,
123 source_line,
124 arch,
125 is_packed: false,
126 is_union: false,
127 }
128}
129
130fn simulate_union_layout(
133 fields: &mut Vec<Field>,
134 name: String,
135 arch: &'static ArchConfig,
136 source_line: Option<u32>,
137) -> StructLayout {
138 for f in fields.iter_mut() {
139 f.offset = 0;
140 }
141 let max_size = fields.iter().map(|f| f.size).max().unwrap_or(0);
142 let max_align = fields.iter().map(|f| f.align).max().unwrap_or(1);
143 let total_size = if max_align > 0 {
144 max_size.next_multiple_of(max_align)
145 } else {
146 max_size
147 };
148
149 StructLayout {
150 name,
151 total_size,
152 align: max_align,
153 fields: std::mem::take(fields),
154 source_file: None,
155 source_line,
156 arch,
157 is_packed: false,
158 is_union: true,
159 }
160}
161
162fn parse_class_specifier(
169 source: &str,
170 node: Node<'_>,
171 arch: &'static ArchConfig,
172) -> Option<StructLayout> {
173 let mut class_name = "<anonymous>".to_string();
174 let mut base_names: Vec<String> = Vec::new();
175 let mut body_node: Option<Node> = None;
176
177 for i in 0..node.child_count() {
178 let child = node.child(i)?;
179 match child.kind() {
180 "type_identifier" => class_name = source[child.byte_range()].to_string(),
181 "base_class_clause" => {
182 for j in 0..child.child_count() {
185 if let Some(base) = child.child(j) {
186 if base.kind() == "type_identifier" {
187 base_names.push(source[base.byte_range()].to_string());
188 }
189 }
190 }
191 }
192 "field_declaration_list" => body_node = Some(child),
193 _ => {}
194 }
195 }
196
197 let body = body_node?;
198
199 let has_virtual = contains_virtual_keyword(source, body);
201
202 let mut raw_fields: Vec<(String, String, Option<String>)> = Vec::new();
204 for i in 0..body.child_count() {
205 if let Some(child) = body.child(i) {
206 if child.kind() == "field_declaration" {
207 if let Some((ty, fname, guard)) = parse_field_declaration(source, child) {
208 raw_fields.push((fname, ty, guard));
209 }
210 }
211 }
212 }
213
214 let mut fields: Vec<Field> = Vec::new();
216
217 if has_virtual {
219 let ps = arch.pointer_size;
220 fields.push(Field {
221 name: "__vptr".to_string(),
222 ty: TypeInfo::Pointer {
223 size: ps,
224 align: ps,
225 },
226 offset: 0,
227 size: ps,
228 align: ps,
229 source_file: None,
230 source_line: None,
231 access: AccessPattern::Unknown,
232 });
233 }
234
235 for base in &base_names {
237 let ps = arch.pointer_size;
238 fields.push(Field {
239 name: format!("__base_{base}"),
240 ty: TypeInfo::Opaque {
241 name: base.clone(),
242 size: ps,
243 align: ps,
244 },
245 offset: 0,
246 size: ps,
247 align: ps,
248 source_file: None,
249 source_line: None,
250 access: AccessPattern::Unknown,
251 });
252 }
253
254 for (fname, ty_name, guard) in raw_fields {
256 let base_ty = strip_bitfield_suffix(&ty_name);
257 let (size, align) = c_type_size_align(base_ty, arch);
258 let access = if let Some(g) = guard {
259 AccessPattern::Concurrent {
260 guard: Some(g),
261 is_atomic: false,
262 }
263 } else {
264 AccessPattern::Unknown
265 };
266 fields.push(Field {
267 name: fname,
268 ty: TypeInfo::Primitive {
269 name: ty_name,
270 size,
271 align,
272 },
273 offset: 0,
274 size,
275 align,
276 source_file: None,
277 source_line: None,
278 access,
279 });
280 }
281
282 if fields.is_empty() {
283 return None;
284 }
285
286 let line = node.start_position().row as u32 + 1;
287 Some(simulate_layout(&mut fields, class_name, arch, Some(line)))
288}
289
290fn contains_virtual_keyword(source: &str, node: Node<'_>) -> bool {
293 let mut stack = vec![node];
294 while let Some(n) = stack.pop() {
295 if n.kind() == "virtual" {
296 return true;
297 }
298 if n.child_count() == 0 {
301 let text = &source[n.byte_range()];
302 if text == "virtual" {
303 return true;
304 }
305 }
306 for i in (0..n.child_count()).rev() {
307 if let Some(child) = n.child(i) {
308 stack.push(child);
309 }
310 }
311 }
312 false
313}
314
315fn extract_structs_from_tree(
318 source: &str,
319 root: Node<'_>,
320 arch: &'static ArchConfig,
321 layouts: &mut Vec<StructLayout>,
322) {
323 let cursor = root.walk();
324 let mut stack = vec![root];
325
326 while let Some(node) = stack.pop() {
327 for i in (0..node.child_count()).rev() {
329 if let Some(child) = node.child(i) {
330 stack.push(child);
331 }
332 }
333
334 match node.kind() {
335 "struct_specifier" => {
336 if let Some(layout) = parse_struct_or_union_specifier(source, node, arch, false) {
337 layouts.push(layout);
338 }
339 }
340 "union_specifier" => {
341 if let Some(layout) = parse_struct_or_union_specifier(source, node, arch, true) {
342 layouts.push(layout);
343 }
344 }
345 "class_specifier" => {
346 if let Some(layout) = parse_class_specifier(source, node, arch) {
347 layouts.push(layout);
348 }
349 }
350 _ => {}
351 }
352 }
353
354 let cursor2 = root.walk();
356 let mut stack2 = vec![root];
357 while let Some(node) = stack2.pop() {
358 for i in (0..node.child_count()).rev() {
359 if let Some(child) = node.child(i) {
360 stack2.push(child);
361 }
362 }
363 if node.kind() == "type_definition" {
364 if let Some(layout) = parse_typedef_struct_or_union(source, node, arch) {
365 let existing = layouts
366 .iter()
367 .position(|l| l.name == layout.name || l.name == "<anonymous>");
368 match existing {
369 Some(i) if layouts[i].name == "<anonymous>" => {
370 layouts[i] = layout;
371 }
372 None => layouts.push(layout),
373 _ => {}
374 }
375 }
376 }
377 }
378 let _ = cursor;
379 let _ = cursor2; }
381
382fn parse_struct_or_union_specifier(
384 source: &str,
385 node: Node<'_>,
386 arch: &'static ArchConfig,
387 is_union: bool,
388) -> Option<StructLayout> {
389 let mut name = "<anonymous>".to_string();
390 let mut body_node: Option<Node> = None;
391
392 for i in 0..node.child_count() {
393 let child = node.child(i)?;
394 match child.kind() {
395 "type_identifier" => name = source[child.byte_range()].to_string(),
396 "field_declaration_list" => body_node = Some(child),
397 _ => {}
398 }
399 }
400
401 let body = body_node?;
402 let mut raw_fields: Vec<(String, String, Option<String>)> = Vec::new();
403
404 for i in 0..body.child_count() {
405 let child = body.child(i)?;
406 if child.kind() == "field_declaration" {
407 if let Some((ty, fname, guard)) = parse_field_declaration(source, child) {
408 raw_fields.push((fname, ty, guard));
409 }
410 }
411 }
412
413 if raw_fields.is_empty() {
414 return None;
415 }
416
417 let mut fields: Vec<Field> = raw_fields
418 .into_iter()
419 .map(|(fname, ty_name, guard)| {
420 let base = strip_bitfield_suffix(&ty_name);
422 let (size, align) = c_type_size_align(base, arch);
423 let access = if let Some(g) = guard {
424 AccessPattern::Concurrent {
425 guard: Some(g),
426 is_atomic: false,
427 }
428 } else {
429 AccessPattern::Unknown
430 };
431 Field {
432 name: fname,
433 ty: TypeInfo::Primitive {
434 name: ty_name,
435 size,
436 align,
437 },
438 offset: 0,
439 size,
440 align,
441 source_file: None,
442 source_line: None,
443 access,
444 }
445 })
446 .collect();
447
448 let line = node.start_position().row as u32 + 1;
449 if is_union {
450 Some(simulate_union_layout(&mut fields, name, arch, Some(line)))
451 } else {
452 Some(simulate_layout(&mut fields, name, arch, Some(line)))
453 }
454}
455
456fn parse_typedef_struct_or_union(
458 source: &str,
459 node: Node<'_>,
460 arch: &'static ArchConfig,
461) -> Option<StructLayout> {
462 let mut specifier_node: Option<Node> = None;
463 let mut is_union = false;
464 let mut typedef_name: Option<String> = None;
465
466 for i in 0..node.child_count() {
467 let child = node.child(i)?;
468 match child.kind() {
469 "struct_specifier" => {
470 specifier_node = Some(child);
471 is_union = false;
472 }
473 "union_specifier" => {
474 specifier_node = Some(child);
475 is_union = true;
476 }
477 "type_identifier" => typedef_name = Some(source[child.byte_range()].to_string()),
478 _ => {}
479 }
480 }
481
482 let spec = specifier_node?;
483 let typedef_name = typedef_name?;
484
485 let mut layout = parse_struct_or_union_specifier(source, spec, arch, is_union)?;
486 if layout.name == "<anonymous>" {
487 layout.name = typedef_name;
488 }
489 Some(layout)
490}
491
492#[allow(dead_code)]
494fn parse_typedef_struct(
495 source: &str,
496 node: Node<'_>,
497 arch: &'static ArchConfig,
498) -> Option<StructLayout> {
499 parse_typedef_struct_or_union(source, node, arch)
500}
501
502fn extract_guard_from_c_field_text(field_source: &str) -> Option<String> {
510 for kw in &["guarded_by", "pt_guarded_by", "GUARDED_BY", "PT_GUARDED_BY"] {
512 if let Some(pos) = field_source.find(kw) {
513 let after = &field_source[pos + kw.len()..];
514 let trimmed = after.trim_start();
516 if let Some(inner) = trimmed.strip_prefix('(') {
517 if let Some(end) = inner.find(')') {
519 let guard = inner[..end].trim().trim_matches('"');
520 if !guard.is_empty() {
521 return Some(guard.to_string());
522 }
523 }
524 }
525 }
526 }
527 None
528}
529
530fn parse_field_declaration(
531 source: &str,
532 node: Node<'_>,
533) -> Option<(String, String, Option<String>)> {
534 let mut ty_parts: Vec<String> = Vec::new();
535 let mut field_name: Option<String> = None;
536 let mut bit_width: Option<String> = None;
538 let mut attr_text = String::new();
540
541 for i in 0..node.child_count() {
542 let child = node.child(i)?;
543 match child.kind() {
544 "type_specifier" | "primitive_type" | "type_identifier" | "sized_type_specifier" => {
545 ty_parts.push(source[child.byte_range()].trim().to_string());
546 }
547 "qualified_identifier" | "template_type" => {
550 ty_parts.push(source[child.byte_range()].trim().to_string());
551 }
552 "struct_specifier" | "union_specifier" => {
556 for j in 0..child.child_count() {
557 if let Some(sub) = child.child(j) {
558 if sub.kind() == "type_identifier" {
559 ty_parts.push(source[sub.byte_range()].trim().to_string());
560 break;
561 }
562 }
563 }
564 }
565 "field_identifier" => {
566 field_name = Some(source[child.byte_range()].trim().to_string());
567 }
568 "pointer_declarator" => {
569 field_name = extract_identifier(source, child);
570 ty_parts.push("*".to_string());
571 }
572 "bitfield_clause" => {
574 let text = source[child.byte_range()].trim();
575 bit_width = Some(text.trim_start_matches(':').trim().to_string());
577 }
578 "attribute_specifier" | "attribute" => {
580 attr_text.push_str(source[child.byte_range()].trim());
581 attr_text.push(' ');
582 }
583 _ => {}
584 }
585 }
586
587 let base_ty = ty_parts.join(" ");
588 let fname = field_name?;
589 if base_ty.is_empty() {
590 return None;
591 }
592 let ty = if let Some(w) = bit_width {
595 format!("{base_ty}:{w}")
596 } else {
597 base_ty
598 };
599
600 let field_src = source[node.byte_range()].to_string();
603 let guard = extract_guard_from_c_field_text(&attr_text)
604 .or_else(|| extract_guard_from_c_field_text(&field_src));
605
606 Some((ty, fname, guard))
607}
608
609fn extract_identifier(source: &str, node: Node<'_>) -> Option<String> {
610 if node.kind() == "field_identifier" || node.kind() == "identifier" {
611 return Some(source[node.byte_range()].to_string());
612 }
613 for i in 0..node.child_count() {
614 if let Some(child) = node.child(i) {
615 if let Some(name) = extract_identifier(source, child) {
616 return Some(name);
617 }
618 }
619 }
620 None
621}
622
623pub fn parse_c(source: &str, arch: &'static ArchConfig) -> anyhow::Result<Vec<StructLayout>> {
626 let mut parser = Parser::new();
627 parser.set_language(&tree_sitter_c::language())?;
628 let tree = parser
629 .parse(source, None)
630 .ok_or_else(|| anyhow::anyhow!("tree-sitter parse failed"))?;
631 let mut layouts = Vec::new();
632 extract_structs_from_tree(source, tree.root_node(), arch, &mut layouts);
633 Ok(layouts)
634}
635
636pub fn parse_cpp(source: &str, arch: &'static ArchConfig) -> anyhow::Result<Vec<StructLayout>> {
637 let mut parser = Parser::new();
638 parser.set_language(&tree_sitter_cpp::language())?;
639 let tree = parser
640 .parse(source, None)
641 .ok_or_else(|| anyhow::anyhow!("tree-sitter parse failed"))?;
642 let mut layouts = Vec::new();
643 extract_structs_from_tree(source, tree.root_node(), arch, &mut layouts);
644 Ok(layouts)
645}
646
647#[cfg(test)]
650mod tests {
651 use super::*;
652 use padlock_core::arch::X86_64_SYSV;
653
654 #[test]
655 fn parse_simple_c_struct() {
656 let src = r#"
657struct Point {
658 int x;
659 int y;
660};
661"#;
662 let layouts = parse_c(src, &X86_64_SYSV).unwrap();
663 assert_eq!(layouts.len(), 1);
664 assert_eq!(layouts[0].name, "Point");
665 assert_eq!(layouts[0].fields.len(), 2);
666 assert_eq!(layouts[0].fields[0].name, "x");
667 assert_eq!(layouts[0].fields[1].name, "y");
668 }
669
670 #[test]
671 fn parse_typedef_struct() {
672 let src = r#"
673typedef struct {
674 char is_active;
675 double timeout;
676 int port;
677} Connection;
678"#;
679 let layouts = parse_c(src, &X86_64_SYSV).unwrap();
680 assert_eq!(layouts.len(), 1);
681 assert_eq!(layouts[0].name, "Connection");
682 assert_eq!(layouts[0].fields.len(), 3);
683 }
684
685 #[test]
686 fn c_layout_computes_offsets() {
687 let src = "struct T { char a; double b; };";
688 let layouts = parse_c(src, &X86_64_SYSV).unwrap();
689 assert_eq!(layouts.len(), 1);
690 let layout = &layouts[0];
691 assert_eq!(layout.fields[0].offset, 0);
693 assert_eq!(layout.fields[1].offset, 8);
694 assert_eq!(layout.total_size, 16);
695 }
696
697 #[test]
698 fn c_layout_detects_padding() {
699 let src = "struct T { char a; int b; };";
700 let layouts = parse_c(src, &X86_64_SYSV).unwrap();
701 let gaps = padlock_core::ir::find_padding(&layouts[0]);
702 assert!(!gaps.is_empty());
703 assert_eq!(gaps[0].bytes, 3); }
705
706 #[test]
707 fn parse_cpp_struct() {
708 let src = "struct Vec3 { float x; float y; float z; };";
709 let layouts = parse_cpp(src, &X86_64_SYSV).unwrap();
710 assert_eq!(layouts.len(), 1);
711 assert_eq!(layouts[0].fields.len(), 3);
712 }
713
714 #[test]
717 fn simd_sse_field_size_and_align() {
718 let src = "struct Vecs { __m128 a; __m256 b; };";
719 let layouts = parse_c(src, &X86_64_SYSV).unwrap();
720 assert_eq!(layouts.len(), 1);
721 let f = &layouts[0].fields;
722 assert_eq!(f[0].size, 16); assert_eq!(f[0].align, 16);
724 assert_eq!(f[1].size, 32); assert_eq!(f[1].align, 32);
726 }
727
728 #[test]
729 fn simd_avx512_size() {
730 let src = "struct Wide { __m512 v; };";
731 let layouts = parse_c(src, &X86_64_SYSV).unwrap();
732 assert_eq!(layouts[0].fields[0].size, 64);
733 assert_eq!(layouts[0].fields[0].align, 64);
734 }
735
736 #[test]
737 fn simd_padding_detected_when_small_field_before_avx() {
738 let src = "struct Mixed { char flag; __m256 data; };";
740 let layouts = parse_c(src, &X86_64_SYSV).unwrap();
741 let gaps = padlock_core::ir::find_padding(&layouts[0]);
742 assert!(!gaps.is_empty());
743 assert_eq!(gaps[0].bytes, 31);
744 }
745
746 #[test]
749 fn union_fields_all_at_offset_zero() {
750 let src = "union Data { int i; float f; double d; };";
751 let layouts = parse_c(src, &X86_64_SYSV).unwrap();
752 assert_eq!(layouts.len(), 1);
753 let u = &layouts[0];
754 assert!(u.is_union);
755 for field in &u.fields {
756 assert_eq!(
757 field.offset, 0,
758 "union field '{}' should be at offset 0",
759 field.name
760 );
761 }
762 }
763
764 #[test]
765 fn union_total_size_is_max_field() {
766 let src = "union Data { int i; float f; double d; };";
768 let layouts = parse_c(src, &X86_64_SYSV).unwrap();
769 assert_eq!(layouts[0].total_size, 8);
770 }
771
772 #[test]
773 fn union_no_padding_finding() {
774 let src = "union Data { int i; double d; };";
775 let layouts = parse_c(src, &X86_64_SYSV).unwrap();
776 let report = padlock_core::findings::Report::from_layouts(&layouts);
777 let sr = &report.structs[0];
778 assert!(!sr
779 .findings
780 .iter()
781 .any(|f| matches!(f, padlock_core::findings::Finding::PaddingWaste { .. })));
782 assert!(!sr
783 .findings
784 .iter()
785 .any(|f| matches!(f, padlock_core::findings::Finding::ReorderSuggestion { .. })));
786 }
787
788 #[test]
789 fn typedef_union_parsed() {
790 let src = "typedef union { int a; double b; } Value;";
791 let layouts = parse_c(src, &X86_64_SYSV).unwrap();
792 assert_eq!(layouts.len(), 1);
793 assert_eq!(layouts[0].name, "Value");
794 assert!(layouts[0].is_union);
795 }
796
797 #[test]
800 fn bitfield_type_annotated_with_width() {
801 let src = "struct Flags { int a : 3; int b : 5; };";
802 let layouts = parse_c(src, &X86_64_SYSV).unwrap();
803 assert_eq!(layouts.len(), 1);
804 let names: Vec<&str> = layouts[0].fields.iter().map(|f| f.name.as_str()).collect();
806 assert!(names.contains(&"a") && names.contains(&"b"));
807 let a_ty = match &layouts[0].fields[0].ty {
809 padlock_core::ir::TypeInfo::Primitive { name, .. } => name.clone(),
810 _ => panic!("expected Primitive"),
811 };
812 assert!(
813 a_ty.contains(':'),
814 "bit field type should contain ':' width annotation"
815 );
816 }
817
818 #[test]
819 fn bitfield_uses_storage_unit_size() {
820 let src = "struct S { int a : 3; };";
822 let layouts = parse_c(src, &X86_64_SYSV).unwrap();
823 assert_eq!(layouts[0].fields[0].size, 4);
824 }
825
826 #[test]
829 fn extract_guard_from_c_guarded_by_macro() {
830 let text = "int value GUARDED_BY(mu);";
831 let guard = extract_guard_from_c_field_text(text);
832 assert_eq!(guard.as_deref(), Some("mu"));
833 }
834
835 #[test]
836 fn extract_guard_from_c_attribute_specifier() {
837 let text = "__attribute__((guarded_by(counter_lock))) uint64_t counter;";
838 let guard = extract_guard_from_c_field_text(text);
839 assert_eq!(guard.as_deref(), Some("counter_lock"));
840 }
841
842 #[test]
843 fn extract_guard_pt_guarded_by() {
844 let text = "int *ptr PT_GUARDED_BY(ptr_lock);";
845 let guard = extract_guard_from_c_field_text(text);
846 assert_eq!(guard.as_deref(), Some("ptr_lock"));
847 }
848
849 #[test]
850 fn no_guard_returns_none() {
851 let guard = extract_guard_from_c_field_text("int x;");
852 assert!(guard.is_none());
853 }
854
855 #[test]
856 fn c_struct_guarded_by_sets_concurrent_access() {
857 let text = "uint64_t readers GUARDED_BY(lock_a);";
861 assert_eq!(
862 extract_guard_from_c_field_text(text).as_deref(),
863 Some("lock_a")
864 );
865 }
866
867 #[test]
868 fn c_struct_different_guards_detected_as_false_sharing() {
869 use padlock_core::arch::X86_64_SYSV;
870 use padlock_core::ir::{AccessPattern, Field, StructLayout, TypeInfo};
871
872 let mut layout = StructLayout {
876 name: "S".into(),
877 total_size: 128,
878 align: 8,
879 fields: vec![
880 Field {
881 name: "readers".into(),
882 ty: TypeInfo::Primitive {
883 name: "uint64_t".into(),
884 size: 8,
885 align: 8,
886 },
887 offset: 0,
888 size: 8,
889 align: 8,
890 source_file: None,
891 source_line: None,
892 access: AccessPattern::Concurrent {
893 guard: Some("lock_a".into()),
894 is_atomic: false,
895 },
896 },
897 Field {
898 name: "writers".into(),
899 ty: TypeInfo::Primitive {
900 name: "uint64_t".into(),
901 size: 8,
902 align: 8,
903 },
904 offset: 8,
905 size: 8,
906 align: 8,
907 source_file: None,
908 source_line: None,
909 access: AccessPattern::Concurrent {
910 guard: Some("lock_b".into()),
911 is_atomic: false,
912 },
913 },
914 ],
915 source_file: None,
916 source_line: None,
917 arch: &X86_64_SYSV,
918 is_packed: false,
919 is_union: false,
920 };
921 assert!(padlock_core::analysis::false_sharing::has_false_sharing(
922 &layout
923 ));
924 layout.fields[1].access = AccessPattern::Concurrent {
926 guard: Some("lock_a".into()),
927 is_atomic: false,
928 };
929 assert!(!padlock_core::analysis::false_sharing::has_false_sharing(
930 &layout
931 ));
932 }
933
934 #[test]
937 fn cpp_class_with_virtual_method_has_vptr() {
938 let src = r#"
939class Widget {
940 virtual void draw();
941 int x;
942 int y;
943};
944"#;
945 let layouts = parse_cpp(src, &X86_64_SYSV).unwrap();
946 assert_eq!(layouts.len(), 1);
947 let l = &layouts[0];
948 assert_eq!(l.fields[0].name, "__vptr");
950 assert_eq!(l.fields[0].size, 8); assert_eq!(l.fields[0].offset, 0);
953 let x = l.fields.iter().find(|f| f.name == "x").unwrap();
955 assert_eq!(x.offset, 8);
956 }
957
958 #[test]
959 fn cpp_class_without_virtual_has_no_vptr() {
960 let src = "class Plain { int a; int b; };";
961 let layouts = parse_cpp(src, &X86_64_SYSV).unwrap();
962 assert_eq!(layouts.len(), 1);
963 assert!(!layouts[0].fields.iter().any(|f| f.name == "__vptr"));
964 }
965
966 #[test]
967 fn cpp_struct_keyword_with_virtual_has_vptr() {
968 let src = "struct IFoo { virtual ~IFoo(); virtual void bar(); };";
970 let layouts = parse_cpp(src, &X86_64_SYSV).unwrap();
971 let _ = layouts; }
975
976 #[test]
979 fn cpp_derived_class_has_base_slot() {
980 let src = r#"
981class Base {
982 int x;
983};
984class Derived : public Base {
985 int y;
986};
987"#;
988 let layouts = parse_cpp(src, &X86_64_SYSV).unwrap();
989 let derived = layouts.iter().find(|l| l.name == "Derived").unwrap();
991 assert!(
993 derived.fields.iter().any(|f| f.name == "__base_Base"),
994 "Derived should have a __base_Base field"
995 );
996 let base_field = derived
998 .fields
999 .iter()
1000 .find(|f| f.name == "__base_Base")
1001 .unwrap();
1002 let y_field = derived.fields.iter().find(|f| f.name == "y").unwrap();
1003 assert!(y_field.offset >= base_field.offset + base_field.size);
1004 }
1005
1006 #[test]
1007 fn cpp_class_multiple_inheritance_has_multiple_base_slots() {
1008 let src = r#"
1009class A { int a; };
1010class B { int b; };
1011class C : public A, public B { int c; };
1012"#;
1013 let layouts = parse_cpp(src, &X86_64_SYSV).unwrap();
1014 let c = layouts.iter().find(|l| l.name == "C").unwrap();
1015 assert!(c.fields.iter().any(|f| f.name == "__base_A"));
1016 assert!(c.fields.iter().any(|f| f.name == "__base_B"));
1017 }
1018
1019 #[test]
1020 fn cpp_virtual_base_class_total_size_accounts_for_vptr() {
1021 let src = "class V { virtual void f(); int x; };";
1023 let layouts = parse_cpp(src, &X86_64_SYSV).unwrap();
1024 let l = &layouts[0];
1025 assert_eq!(l.total_size, 16);
1027 }
1028}