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
136#[derive(Debug, Clone, PartialEq, serde::Serialize)]
137pub struct PaddingGap {
138 pub after_field: String,
139 pub bytes: usize,
140 pub at_offset: usize,
141}
142
143#[derive(Debug, Clone, serde::Serialize)]
144pub struct SharingConflict {
145 pub fields: Vec<String>,
146 pub cache_line: usize,
147}
148
149pub fn find_padding(layout: &StructLayout) -> Vec<PaddingGap> {
154 if layout.is_union {
155 return Vec::new();
156 }
157 let mut gaps = Vec::new();
158 for window in layout.fields.windows(2) {
159 let current = &window[0];
160 let next = &window[1];
161 let end = current.offset + current.size;
162 if next.offset > end {
163 gaps.push(PaddingGap {
164 after_field: current.name.clone(),
165 bytes: next.offset - end,
166 at_offset: end,
167 });
168 }
169 }
170 if let Some(last) = layout.fields.last() {
172 let end = last.offset + last.size;
173 if layout.total_size > end {
174 gaps.push(PaddingGap {
175 after_field: last.name.clone(),
176 bytes: layout.total_size - end,
177 at_offset: end,
178 });
179 }
180 }
181 gaps
182}
183
184pub fn optimal_order(layout: &StructLayout) -> Vec<&Field> {
186 let mut sorted: Vec<&Field> = layout.fields.iter().collect();
187 sorted.sort_by(|a, b| {
188 b.align
189 .cmp(&a.align)
190 .then(b.size.cmp(&a.size))
191 .then(a.name.cmp(&b.name))
192 });
193 sorted
194}
195
196#[cfg(any(test, feature = "test-helpers"))]
199pub mod test_fixtures {
200 use super::*;
201 use crate::arch::X86_64_SYSV;
202
203 pub fn connection_layout() -> StructLayout {
212 StructLayout {
213 name: "Connection".to_string(),
214 total_size: 24,
215 align: 8,
216 fields: vec![
217 Field {
218 name: "is_active".into(),
219 ty: TypeInfo::Primitive {
220 name: "bool".into(),
221 size: 1,
222 align: 1,
223 },
224 offset: 0,
225 size: 1,
226 align: 1,
227 source_file: None,
228 source_line: None,
229 access: AccessPattern::Unknown,
230 },
231 Field {
232 name: "timeout".into(),
233 ty: TypeInfo::Primitive {
234 name: "f64".into(),
235 size: 8,
236 align: 8,
237 },
238 offset: 8,
239 size: 8,
240 align: 8,
241 source_file: None,
242 source_line: None,
243 access: AccessPattern::Unknown,
244 },
245 Field {
246 name: "is_tls".into(),
247 ty: TypeInfo::Primitive {
248 name: "bool".into(),
249 size: 1,
250 align: 1,
251 },
252 offset: 16,
253 size: 1,
254 align: 1,
255 source_file: None,
256 source_line: None,
257 access: AccessPattern::Unknown,
258 },
259 Field {
260 name: "port".into(),
261 ty: TypeInfo::Primitive {
262 name: "i32".into(),
263 size: 4,
264 align: 4,
265 },
266 offset: 20,
267 size: 4,
268 align: 4,
269 source_file: None,
270 source_line: None,
271 access: AccessPattern::Unknown,
272 },
273 ],
274 source_file: None,
275 source_line: None,
276 arch: &X86_64_SYSV,
277 is_packed: false,
278 is_union: false,
279 is_repr_rust: false,
280 suppressed_findings: Vec::new(),
281 }
282 }
283
284 pub fn packed_layout() -> StructLayout {
286 StructLayout {
287 name: "Packed".to_string(),
288 total_size: 8,
289 align: 4,
290 fields: vec![
291 Field {
292 name: "a".into(),
293 ty: TypeInfo::Primitive {
294 name: "i32".into(),
295 size: 4,
296 align: 4,
297 },
298 offset: 0,
299 size: 4,
300 align: 4,
301 source_file: None,
302 source_line: None,
303 access: AccessPattern::Unknown,
304 },
305 Field {
306 name: "b".into(),
307 ty: TypeInfo::Primitive {
308 name: "i16".into(),
309 size: 2,
310 align: 2,
311 },
312 offset: 4,
313 size: 2,
314 align: 2,
315 source_file: None,
316 source_line: None,
317 access: AccessPattern::Unknown,
318 },
319 Field {
320 name: "c".into(),
321 ty: TypeInfo::Primitive {
322 name: "i16".into(),
323 size: 2,
324 align: 2,
325 },
326 offset: 6,
327 size: 2,
328 align: 2,
329 source_file: None,
330 source_line: None,
331 access: AccessPattern::Unknown,
332 },
333 ],
334 source_file: None,
335 source_line: None,
336 arch: &X86_64_SYSV,
337 is_packed: false,
338 is_union: false,
339 is_repr_rust: false,
340 suppressed_findings: Vec::new(),
341 }
342 }
343
344 #[test]
345 fn test_find_padding_connection() {
346 let layout = connection_layout();
347 let gaps = find_padding(&layout);
348 assert_eq!(
349 gaps,
350 vec![
351 PaddingGap {
352 after_field: "is_active".into(),
353 bytes: 7,
354 at_offset: 1
355 },
356 PaddingGap {
357 after_field: "is_tls".into(),
358 bytes: 3,
359 at_offset: 17
360 },
361 ]
362 );
363 }
364
365 #[test]
366 fn test_find_padding_packed() {
367 let layout = packed_layout();
368 assert!(find_padding(&layout).is_empty());
369 }
370
371 #[test]
372 fn test_optimal_order() {
373 let layout = connection_layout();
374 let order: Vec<&str> = optimal_order(&layout)
375 .iter()
376 .map(|f| f.name.as_str())
377 .collect();
378 assert_eq!(order[0], "timeout");
380 assert_eq!(order[1], "port");
381 assert!(order[2] == "is_active" || order[2] == "is_tls");
382 }
383}