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