struct_audit/output/
table.rs

1use crate::types::StructLayout;
2use colored::Colorize;
3use comfy_table::{Cell, CellAlignment, Color, Table, presets::UTF8_FULL_CONDENSED};
4
5pub struct TableFormatter {
6    no_color: bool,
7    cache_line_size: u32,
8}
9
10impl TableFormatter {
11    pub fn new(no_color: bool, cache_line_size: u32) -> Self {
12        Self { no_color, cache_line_size }
13    }
14
15    pub fn format(&self, layouts: &[StructLayout]) -> String {
16        let mut output = String::new();
17
18        for (i, layout) in layouts.iter().enumerate() {
19            if i > 0 {
20                output.push_str("\n\n");
21            }
22            output.push_str(&self.format_struct(layout));
23        }
24
25        output
26    }
27
28    fn format_struct(&self, layout: &StructLayout) -> String {
29        let mut output = String::new();
30
31        let header = format!(
32            "struct {} ({} bytes, {:.1}% padding, {} cache line{})",
33            layout.name,
34            layout.size,
35            layout.metrics.padding_percentage,
36            layout.metrics.cache_lines_spanned,
37            if layout.metrics.cache_lines_spanned == 1 { "" } else { "s" }
38        );
39
40        if self.no_color {
41            output.push_str(&header);
42        } else {
43            output.push_str(&header.bold().to_string());
44        }
45        output.push('\n');
46
47        if let Some(ref loc) = layout.source_location {
48            output.push_str(&format!("  defined at {}:{}\n", loc.file, loc.line));
49        }
50        output.push('\n');
51
52        let mut table = Table::new();
53        table.load_preset(UTF8_FULL_CONDENSED);
54        table.set_header(vec!["Offset", "Size", "Type", "Field"]);
55
56        let mut entries: Vec<TableEntry> = Vec::new();
57
58        let mut padding_iter = layout.metrics.padding_holes.iter().peekable();
59
60        for member in &layout.members {
61            while let Some(hole) = padding_iter.peek() {
62                if member.offset.map(|o| hole.offset < o).unwrap_or(false) {
63                    let hole = padding_iter.next().unwrap();
64                    entries.push(TableEntry::Padding { offset: hole.offset, size: hole.size });
65                } else {
66                    break;
67                }
68            }
69
70            entries.push(TableEntry::Member {
71                offset: member.offset,
72                size: member.size,
73                type_name: &member.type_name,
74                name: &member.name,
75                bit_offset: member.bit_offset,
76                bit_size: member.bit_size,
77            });
78        }
79
80        for hole in padding_iter {
81            entries.push(TableEntry::Padding { offset: hole.offset, size: hole.size });
82        }
83
84        entries.sort_by_key(|e| match e {
85            TableEntry::Member { offset, .. } => offset.unwrap_or(u64::MAX),
86            TableEntry::Padding { offset, .. } => *offset,
87        });
88
89        let mut last_cache_line: Option<u64> = None;
90
91        for entry in &entries {
92            let offset = match entry {
93                TableEntry::Member { offset: Some(o), .. } => Some(*o),
94                TableEntry::Member { offset: None, .. } => None,
95                TableEntry::Padding { offset, .. } => Some(*offset),
96            };
97
98            if let Some(off) = offset {
99                let current_cache_line = off / self.cache_line_size as u64;
100                if last_cache_line.is_some_and(|l| l != current_cache_line) {
101                    let marker_offset = current_cache_line * self.cache_line_size as u64;
102                    table.add_row(vec![
103                        Cell::new(format!(
104                            "--- cache line {} ({}) ---",
105                            current_cache_line, marker_offset
106                        ))
107                        .set_alignment(CellAlignment::Center),
108                        Cell::new(""),
109                        Cell::new(""),
110                        Cell::new(""),
111                    ]);
112                }
113                last_cache_line = Some(current_cache_line);
114            }
115
116            match entry {
117                TableEntry::Member { offset, size, type_name, name, bit_offset, bit_size } => {
118                    let offset_str = match (offset, bit_offset) {
119                        (Some(o), Some(bo)) => format!("{}:{}", o, bo),
120                        (Some(o), None) => o.to_string(),
121                        (None, Some(bo)) => format!("?:{}", bo),
122                        (None, None) => "?".to_string(),
123                    };
124                    let size_str = match (size, bit_size) {
125                        (_, Some(bs)) => format!("{}b", bs),
126                        (Some(s), None) => s.to_string(),
127                        (None, None) => "?".to_string(),
128                    };
129                    table.add_row(vec![
130                        Cell::new(offset_str),
131                        Cell::new(size_str),
132                        Cell::new(type_name.to_string()),
133                        Cell::new(name.to_string()),
134                    ]);
135                }
136                TableEntry::Padding { offset, size } => {
137                    let row = if self.no_color {
138                        vec![
139                            Cell::new(offset.to_string()),
140                            Cell::new(format!("[{} bytes]", size)),
141                            Cell::new("---"),
142                            Cell::new("PAD"),
143                        ]
144                    } else {
145                        vec![
146                            Cell::new(offset.to_string()).fg(Color::Yellow),
147                            Cell::new(format!("[{} bytes]", size)).fg(Color::Yellow),
148                            Cell::new("---").fg(Color::Yellow),
149                            Cell::new("PAD").fg(Color::Yellow),
150                        ]
151                    };
152                    table.add_row(row);
153                }
154            }
155        }
156
157        output.push_str(&table.to_string());
158
159        output.push_str(&format!(
160            "\n\nSummary: {} useful bytes, {} padding bytes ({:.1}%), cache density: {:.1}%\n",
161            layout.metrics.useful_size,
162            layout.metrics.padding_bytes,
163            layout.metrics.padding_percentage,
164            layout.metrics.cache_line_density
165        ));
166
167        output
168    }
169}
170
171enum TableEntry<'a> {
172    Member {
173        offset: Option<u64>,
174        size: Option<u64>,
175        type_name: &'a str,
176        name: &'a str,
177        bit_offset: Option<u64>,
178        bit_size: Option<u64>,
179    },
180    Padding {
181        offset: u64,
182        size: u64,
183    },
184}