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 #[serde(default)]
83 is_annotated: bool,
84 },
85 ReadMostly,
86 Padding,
87}
88
89#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
90pub struct Field {
91 pub name: String,
92 pub ty: TypeInfo,
93 pub offset: usize,
94 pub size: usize,
95 pub align: usize,
96 pub source_file: Option<String>,
97 pub source_line: Option<u32>,
98 pub access: AccessPattern,
99}
100
101#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
103pub struct StructLayout {
104 pub name: String,
105 pub total_size: usize,
106 pub align: usize,
107 pub fields: Vec<Field>,
108 pub source_file: Option<String>,
109 pub source_line: Option<u32>,
110 #[serde(with = "arch_serde")]
111 pub arch: &'static ArchConfig,
112 pub is_packed: bool,
113 pub is_union: bool,
117 #[serde(default)]
123 pub is_repr_rust: bool,
124 #[serde(default, skip_serializing_if = "Vec::is_empty")]
133 pub suppressed_findings: Vec<String>,
134
135 #[serde(default, skip_serializing_if = "Vec::is_empty")]
142 pub uncertain_fields: Vec<String>,
143}
144
145#[derive(Debug, Clone, PartialEq, serde::Serialize)]
146pub struct PaddingGap {
147 pub after_field: String,
148 pub bytes: usize,
149 pub at_offset: usize,
150}
151
152#[derive(Debug, Clone, serde::Serialize)]
153pub struct SharingConflict {
154 pub fields: Vec<String>,
155 pub cache_line: usize,
156}
157
158pub fn find_padding(layout: &StructLayout) -> Vec<PaddingGap> {
163 if layout.is_union {
164 return Vec::new();
165 }
166 let mut gaps = Vec::new();
167 for window in layout.fields.windows(2) {
168 let current = &window[0];
169 let next = &window[1];
170 let end = current.offset + current.size;
171 if next.offset > end {
172 gaps.push(PaddingGap {
173 after_field: current.name.clone(),
174 bytes: next.offset - end,
175 at_offset: end,
176 });
177 }
178 }
179 if let Some(last) = layout.fields.last() {
181 let end = last.offset + last.size;
182 if layout.total_size > end {
183 gaps.push(PaddingGap {
184 after_field: last.name.clone(),
185 bytes: layout.total_size - end,
186 at_offset: end,
187 });
188 }
189 }
190 gaps
191}
192
193pub fn optimal_order(layout: &StructLayout) -> Vec<&Field> {
206 let mut sorted: Vec<&Field> = layout.fields.iter().collect();
207 sorted.sort_by(|a, b| {
208 b.align
209 .cmp(&a.align)
210 .then(b.size.cmp(&a.size))
211 .then(a.name.cmp(&b.name))
212 });
213 sorted
214}
215
216#[cfg(any(test, feature = "test-helpers"))]
219pub mod test_fixtures {
220 use super::*;
221 use crate::arch::X86_64_SYSV;
222
223 pub fn connection_layout() -> StructLayout {
232 StructLayout {
233 name: "Connection".to_string(),
234 total_size: 24,
235 align: 8,
236 fields: vec![
237 Field {
238 name: "is_active".into(),
239 ty: TypeInfo::Primitive {
240 name: "bool".into(),
241 size: 1,
242 align: 1,
243 },
244 offset: 0,
245 size: 1,
246 align: 1,
247 source_file: None,
248 source_line: None,
249 access: AccessPattern::Unknown,
250 },
251 Field {
252 name: "timeout".into(),
253 ty: TypeInfo::Primitive {
254 name: "f64".into(),
255 size: 8,
256 align: 8,
257 },
258 offset: 8,
259 size: 8,
260 align: 8,
261 source_file: None,
262 source_line: None,
263 access: AccessPattern::Unknown,
264 },
265 Field {
266 name: "is_tls".into(),
267 ty: TypeInfo::Primitive {
268 name: "bool".into(),
269 size: 1,
270 align: 1,
271 },
272 offset: 16,
273 size: 1,
274 align: 1,
275 source_file: None,
276 source_line: None,
277 access: AccessPattern::Unknown,
278 },
279 Field {
280 name: "port".into(),
281 ty: TypeInfo::Primitive {
282 name: "i32".into(),
283 size: 4,
284 align: 4,
285 },
286 offset: 20,
287 size: 4,
288 align: 4,
289 source_file: None,
290 source_line: None,
291 access: AccessPattern::Unknown,
292 },
293 ],
294 source_file: None,
295 source_line: None,
296 arch: &X86_64_SYSV,
297 is_packed: false,
298 is_union: false,
299 is_repr_rust: false,
300 suppressed_findings: Vec::new(),
301 uncertain_fields: Vec::new(),
302 }
303 }
304
305 pub fn packed_layout() -> StructLayout {
307 StructLayout {
308 name: "Packed".to_string(),
309 total_size: 8,
310 align: 4,
311 fields: vec![
312 Field {
313 name: "a".into(),
314 ty: TypeInfo::Primitive {
315 name: "i32".into(),
316 size: 4,
317 align: 4,
318 },
319 offset: 0,
320 size: 4,
321 align: 4,
322 source_file: None,
323 source_line: None,
324 access: AccessPattern::Unknown,
325 },
326 Field {
327 name: "b".into(),
328 ty: TypeInfo::Primitive {
329 name: "i16".into(),
330 size: 2,
331 align: 2,
332 },
333 offset: 4,
334 size: 2,
335 align: 2,
336 source_file: None,
337 source_line: None,
338 access: AccessPattern::Unknown,
339 },
340 Field {
341 name: "c".into(),
342 ty: TypeInfo::Primitive {
343 name: "i16".into(),
344 size: 2,
345 align: 2,
346 },
347 offset: 6,
348 size: 2,
349 align: 2,
350 source_file: None,
351 source_line: None,
352 access: AccessPattern::Unknown,
353 },
354 ],
355 source_file: None,
356 source_line: None,
357 arch: &X86_64_SYSV,
358 is_packed: false,
359 is_union: false,
360 is_repr_rust: false,
361 suppressed_findings: Vec::new(),
362 uncertain_fields: Vec::new(),
363 }
364 }
365
366 #[test]
367 fn test_find_padding_connection() {
368 let layout = connection_layout();
369 let gaps = find_padding(&layout);
370 assert_eq!(
371 gaps,
372 vec![
373 PaddingGap {
374 after_field: "is_active".into(),
375 bytes: 7,
376 at_offset: 1
377 },
378 PaddingGap {
379 after_field: "is_tls".into(),
380 bytes: 3,
381 at_offset: 17
382 },
383 ]
384 );
385 }
386
387 #[test]
388 fn test_find_padding_packed() {
389 let layout = packed_layout();
390 assert!(find_padding(&layout).is_empty());
391 }
392
393 #[test]
394 fn test_optimal_order() {
395 let layout = connection_layout();
396 let order: Vec<&str> = optimal_order(&layout)
397 .iter()
398 .map(|f| f.name.as_str())
399 .collect();
400 assert_eq!(order[0], "timeout");
402 assert_eq!(order[1], "port");
403 assert!(order[2] == "is_active" || order[2] == "is_tls");
404 }
405}