1pub use crate::arch::{ArchConfig, X86_64_SYSV};
4
5mod arch_serde {
7 use crate::arch::{ArchConfig, arch_by_name};
8 use serde::{Deserialize, Deserializer, Serializer};
9
10 pub fn serialize<S: Serializer>(arch: &&'static ArchConfig, s: S) -> Result<S::Ok, S::Error> {
11 s.serialize_str(arch.name)
12 }
13
14 pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<&'static ArchConfig, D::Error> {
15 let name = String::deserialize(d)?;
16 arch_by_name(&name).ok_or_else(|| {
17 serde::de::Error::custom(format!(
18 "unknown arch {name:?} in cache; \
19 clear it with `rm -rf .padlock-cache`"
20 ))
21 })
22 }
23}
24
25#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
27pub enum TypeInfo {
28 Primitive {
29 name: String,
30 size: usize,
31 align: usize,
32 },
33 Pointer {
34 size: usize,
35 align: usize,
36 },
37 Array {
38 element: Box<TypeInfo>,
39 count: usize,
40 size: usize,
41 align: usize,
42 },
43 Struct(Box<StructLayout>),
44 Opaque {
45 name: String,
46 size: usize,
47 align: usize,
48 },
49}
50
51impl TypeInfo {
52 pub fn size(&self) -> usize {
53 match self {
54 TypeInfo::Primitive { size, .. } => *size,
55 TypeInfo::Pointer { size, .. } => *size,
56 TypeInfo::Array { size, .. } => *size,
57 TypeInfo::Struct(l) => l.total_size,
58 TypeInfo::Opaque { size, .. } => *size,
59 }
60 }
61
62 pub fn align(&self) -> usize {
63 match self {
64 TypeInfo::Primitive { align, .. } => *align,
65 TypeInfo::Pointer { align, .. } => *align,
66 TypeInfo::Array { align, .. } => *align,
67 TypeInfo::Struct(l) => l.align,
68 TypeInfo::Opaque { align, .. } => *align,
69 }
70 }
71}
72
73#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
74pub enum AccessPattern {
75 Unknown,
76 Concurrent {
77 guard: Option<String>,
78 is_atomic: bool,
79 },
80 ReadMostly,
81 Padding,
82}
83
84#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
85pub struct Field {
86 pub name: String,
87 pub ty: TypeInfo,
88 pub offset: usize,
89 pub size: usize,
90 pub align: usize,
91 pub source_file: Option<String>,
92 pub source_line: Option<u32>,
93 pub access: AccessPattern,
94}
95
96#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
98pub struct StructLayout {
99 pub name: String,
100 pub total_size: usize,
101 pub align: usize,
102 pub fields: Vec<Field>,
103 pub source_file: Option<String>,
104 pub source_line: Option<u32>,
105 #[serde(with = "arch_serde")]
106 pub arch: &'static ArchConfig,
107 pub is_packed: bool,
108 pub is_union: bool,
112 #[serde(default)]
118 pub is_repr_rust: bool,
119 #[serde(default, skip_serializing_if = "Vec::is_empty")]
128 pub suppressed_findings: Vec<String>,
129}
130
131#[derive(Debug, Clone, PartialEq, serde::Serialize)]
132pub struct PaddingGap {
133 pub after_field: String,
134 pub bytes: usize,
135 pub at_offset: usize,
136}
137
138#[derive(Debug, Clone, serde::Serialize)]
139pub struct SharingConflict {
140 pub fields: Vec<String>,
141 pub cache_line: usize,
142}
143
144pub fn find_padding(layout: &StructLayout) -> Vec<PaddingGap> {
149 if layout.is_union {
150 return Vec::new();
151 }
152 let mut gaps = Vec::new();
153 for window in layout.fields.windows(2) {
154 let current = &window[0];
155 let next = &window[1];
156 let end = current.offset + current.size;
157 if next.offset > end {
158 gaps.push(PaddingGap {
159 after_field: current.name.clone(),
160 bytes: next.offset - end,
161 at_offset: end,
162 });
163 }
164 }
165 if let Some(last) = layout.fields.last() {
167 let end = last.offset + last.size;
168 if layout.total_size > end {
169 gaps.push(PaddingGap {
170 after_field: last.name.clone(),
171 bytes: layout.total_size - end,
172 at_offset: end,
173 });
174 }
175 }
176 gaps
177}
178
179pub fn optimal_order(layout: &StructLayout) -> Vec<&Field> {
181 let mut sorted: Vec<&Field> = layout.fields.iter().collect();
182 sorted.sort_by(|a, b| {
183 b.align
184 .cmp(&a.align)
185 .then(b.size.cmp(&a.size))
186 .then(a.name.cmp(&b.name))
187 });
188 sorted
189}
190
191#[cfg(any(test, feature = "test-helpers"))]
194pub mod test_fixtures {
195 use super::*;
196 use crate::arch::X86_64_SYSV;
197
198 pub fn connection_layout() -> StructLayout {
207 StructLayout {
208 name: "Connection".to_string(),
209 total_size: 24,
210 align: 8,
211 fields: vec![
212 Field {
213 name: "is_active".into(),
214 ty: TypeInfo::Primitive {
215 name: "bool".into(),
216 size: 1,
217 align: 1,
218 },
219 offset: 0,
220 size: 1,
221 align: 1,
222 source_file: None,
223 source_line: None,
224 access: AccessPattern::Unknown,
225 },
226 Field {
227 name: "timeout".into(),
228 ty: TypeInfo::Primitive {
229 name: "f64".into(),
230 size: 8,
231 align: 8,
232 },
233 offset: 8,
234 size: 8,
235 align: 8,
236 source_file: None,
237 source_line: None,
238 access: AccessPattern::Unknown,
239 },
240 Field {
241 name: "is_tls".into(),
242 ty: TypeInfo::Primitive {
243 name: "bool".into(),
244 size: 1,
245 align: 1,
246 },
247 offset: 16,
248 size: 1,
249 align: 1,
250 source_file: None,
251 source_line: None,
252 access: AccessPattern::Unknown,
253 },
254 Field {
255 name: "port".into(),
256 ty: TypeInfo::Primitive {
257 name: "i32".into(),
258 size: 4,
259 align: 4,
260 },
261 offset: 20,
262 size: 4,
263 align: 4,
264 source_file: None,
265 source_line: None,
266 access: AccessPattern::Unknown,
267 },
268 ],
269 source_file: None,
270 source_line: None,
271 arch: &X86_64_SYSV,
272 is_packed: false,
273 is_union: false,
274 is_repr_rust: false,
275 suppressed_findings: Vec::new(),
276 }
277 }
278
279 pub fn packed_layout() -> StructLayout {
281 StructLayout {
282 name: "Packed".to_string(),
283 total_size: 8,
284 align: 4,
285 fields: vec![
286 Field {
287 name: "a".into(),
288 ty: TypeInfo::Primitive {
289 name: "i32".into(),
290 size: 4,
291 align: 4,
292 },
293 offset: 0,
294 size: 4,
295 align: 4,
296 source_file: None,
297 source_line: None,
298 access: AccessPattern::Unknown,
299 },
300 Field {
301 name: "b".into(),
302 ty: TypeInfo::Primitive {
303 name: "i16".into(),
304 size: 2,
305 align: 2,
306 },
307 offset: 4,
308 size: 2,
309 align: 2,
310 source_file: None,
311 source_line: None,
312 access: AccessPattern::Unknown,
313 },
314 Field {
315 name: "c".into(),
316 ty: TypeInfo::Primitive {
317 name: "i16".into(),
318 size: 2,
319 align: 2,
320 },
321 offset: 6,
322 size: 2,
323 align: 2,
324 source_file: None,
325 source_line: None,
326 access: AccessPattern::Unknown,
327 },
328 ],
329 source_file: None,
330 source_line: None,
331 arch: &X86_64_SYSV,
332 is_packed: false,
333 is_union: false,
334 is_repr_rust: false,
335 suppressed_findings: Vec::new(),
336 }
337 }
338
339 #[test]
340 fn test_find_padding_connection() {
341 let layout = connection_layout();
342 let gaps = find_padding(&layout);
343 assert_eq!(
344 gaps,
345 vec![
346 PaddingGap {
347 after_field: "is_active".into(),
348 bytes: 7,
349 at_offset: 1
350 },
351 PaddingGap {
352 after_field: "is_tls".into(),
353 bytes: 3,
354 at_offset: 17
355 },
356 ]
357 );
358 }
359
360 #[test]
361 fn test_find_padding_packed() {
362 let layout = packed_layout();
363 assert!(find_padding(&layout).is_empty());
364 }
365
366 #[test]
367 fn test_optimal_order() {
368 let layout = connection_layout();
369 let order: Vec<&str> = optimal_order(&layout)
370 .iter()
371 .map(|f| f.name.as_str())
372 .collect();
373 assert_eq!(order[0], "timeout");
375 assert_eq!(order[1], "port");
376 assert!(order[2] == "is_active" || order[2] == "is_tls");
377 }
378}