layout_audit/
types.rs

1use serde::Serialize;
2
3#[derive(Debug, Clone, Serialize)]
4pub struct StructLayout {
5    pub name: String,
6    pub size: u64,
7    pub alignment: Option<u64>,
8    pub members: Vec<MemberLayout>,
9    pub metrics: LayoutMetrics,
10    #[serde(skip_serializing_if = "Option::is_none")]
11    pub source_location: Option<SourceLocation>,
12}
13
14#[derive(Debug, Clone, Serialize)]
15pub struct MemberLayout {
16    pub name: String,
17    pub type_name: String,
18    pub offset: Option<u64>,
19    pub size: Option<u64>,
20    #[serde(skip_serializing_if = "Option::is_none")]
21    pub bit_offset: Option<u64>,
22    #[serde(skip_serializing_if = "Option::is_none")]
23    pub bit_size: Option<u64>,
24    /// True if the type was marked with DW_TAG_atomic_type in DWARF debug info.
25    /// This provides more reliable atomic detection than string pattern matching.
26    #[serde(skip_serializing_if = "std::ops::Not::not")]
27    pub is_atomic: bool,
28}
29
30#[derive(Debug, Clone, Serialize, Default)]
31pub struct LayoutMetrics {
32    pub total_size: u64,
33    pub useful_size: u64,
34    pub padding_bytes: u64,
35    pub padding_percentage: f64,
36    pub cache_lines_spanned: u32,
37    pub cache_line_density: f64,
38    pub padding_holes: Vec<PaddingHole>,
39    #[serde(skip_serializing_if = "std::ops::Not::not")]
40    pub partial: bool,
41    #[serde(skip_serializing_if = "Option::is_none")]
42    pub false_sharing: Option<FalseSharingAnalysis>,
43}
44
45#[derive(Debug, Clone, Serialize)]
46pub struct PaddingHole {
47    pub offset: u64,
48    pub size: u64,
49    pub after_member: Option<String>,
50}
51
52#[derive(Debug, Clone, Serialize)]
53pub struct SourceLocation {
54    pub file: String,
55    pub line: u64,
56}
57
58#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
59pub struct FalseSharingWarning {
60    pub member_a: String,
61    pub member_b: String,
62    pub cache_line: u64,
63    /// Gap in bytes between member_a's end and member_b's start.
64    /// Negative = overlap, Zero = adjacent, Positive = gap
65    pub gap_bytes: i64,
66}
67
68#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
69pub struct CacheLineSpanningWarning {
70    pub member: String,
71    pub type_name: String,
72    pub offset: u64,
73    pub size: u64,
74    pub start_cache_line: u64,
75    pub end_cache_line: u64,
76    pub lines_spanned: u64,
77}
78
79#[derive(Debug, Clone, Serialize, Default, PartialEq, Eq)]
80pub struct FalseSharingAnalysis {
81    pub atomic_members: Vec<AtomicMember>,
82    pub warnings: Vec<FalseSharingWarning>,
83    #[serde(skip_serializing_if = "Vec::is_empty")]
84    pub spanning_warnings: Vec<CacheLineSpanningWarning>,
85}
86
87#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
88pub struct AtomicMember {
89    pub name: String,
90    pub type_name: String,
91    pub offset: u64,
92    pub size: u64,
93    pub cache_line: u64,
94    pub end_cache_line: u64,
95    pub spans_cache_lines: bool,
96}
97
98impl StructLayout {
99    pub fn new(name: String, size: u64, alignment: Option<u64>) -> Self {
100        Self {
101            name,
102            size,
103            alignment,
104            members: Vec::new(),
105            metrics: LayoutMetrics::default(),
106            source_location: None,
107        }
108    }
109}
110
111impl MemberLayout {
112    pub fn new(name: String, type_name: String, offset: Option<u64>, size: Option<u64>) -> Self {
113        Self { name, type_name, offset, size, bit_offset: None, bit_size: None, is_atomic: false }
114    }
115
116    pub fn with_atomic(mut self, is_atomic: bool) -> Self {
117        self.is_atomic = is_atomic;
118        self
119    }
120
121    pub fn end_offset(&self) -> Option<u64> {
122        match (self.offset, self.size) {
123            // Use checked_add to prevent overflow for malformed DWARF data.
124            (Some(off), Some(sz)) => off.checked_add(sz),
125            _ => None,
126        }
127    }
128}
129
130#[cfg(test)]
131mod tests {
132    use super::*;
133
134    #[test]
135    fn member_end_offset_handles_overflow() {
136        let member = MemberLayout::new("a".to_string(), "u8".to_string(), Some(u64::MAX), Some(1));
137        assert_eq!(member.end_offset(), None);
138    }
139
140    #[test]
141    fn member_end_offset_ok() {
142        let member = MemberLayout::new("a".to_string(), "u8".to_string(), Some(4), Some(2));
143        assert_eq!(member.end_offset(), Some(6));
144    }
145
146    #[test]
147    fn member_with_atomic_flag() {
148        let member = MemberLayout::new("a".to_string(), "u32".to_string(), Some(0), Some(4))
149            .with_atomic(true);
150        assert!(member.is_atomic);
151    }
152
153    #[test]
154    fn struct_new_initializes_fields() {
155        let s = StructLayout::new("Foo".to_string(), 16, Some(8));
156        assert_eq!(s.name, "Foo");
157        assert_eq!(s.size, 16);
158        assert_eq!(s.alignment, Some(8));
159        assert!(s.members.is_empty());
160    }
161}