Skip to main content

padlock_source/frontends/
rust.rs

1// padlock-source/src/frontends/rust.rs
2//
3// Extracts struct layouts from Rust source using syn + the Visit API.
4// Sizes are approximated from type names using the target arch config.
5// Only repr(C) / repr(packed) / plain structs are handled; generics are opaque.
6
7use padlock_core::arch::ArchConfig;
8use padlock_core::ir::{AccessPattern, Field, StructLayout, TypeInfo};
9use quote::ToTokens;
10use syn::{visit::Visit, Fields, ItemStruct, Type};
11
12// ── attribute guard extraction ────────────────────────────────────────────────
13
14/// Extract a lock guard name from field attributes.
15///
16/// Recognised forms:
17/// - `#[lock_protected_by = "mu"]`
18/// - `#[protected_by = "mu"]`
19/// - `#[guarded_by("mu")]` or `#[guarded_by(mu)]`
20/// - `#[pt_guarded_by("mu")]` or `#[pt_guarded_by(mu)]` (pointer variant)
21pub fn extract_guard_from_attrs(attrs: &[syn::Attribute]) -> Option<String> {
22    for attr in attrs {
23        let path = attr.path();
24        // Name-value form: #[lock_protected_by = "mu"] / #[protected_by = "mu"]
25        if path.is_ident("lock_protected_by") || path.is_ident("protected_by") {
26            if let syn::Meta::NameValue(nv) = &attr.meta {
27                if let syn::Expr::Lit(syn::ExprLit {
28                    lit: syn::Lit::Str(s),
29                    ..
30                }) = &nv.value
31                {
32                    return Some(s.value());
33                }
34            }
35        }
36        // List form: #[guarded_by("mu")] / #[guarded_by(mu)] / #[pt_guarded_by(...)]
37        if path.is_ident("guarded_by") || path.is_ident("pt_guarded_by") {
38            // Try string literal first
39            if let Ok(s) = attr.parse_args::<syn::LitStr>() {
40                return Some(s.value());
41            }
42            // Fall back to bare identifier
43            if let Ok(id) = attr.parse_args::<syn::Ident>() {
44                return Some(id.to_string());
45            }
46        }
47    }
48    None
49}
50
51// ── type resolution ───────────────────────────────────────────────────────────
52
53fn rust_type_size_align(ty: &Type, arch: &'static ArchConfig) -> (usize, usize, TypeInfo) {
54    match ty {
55        Type::Path(tp) => {
56            let name = tp
57                .path
58                .segments
59                .last()
60                .map(|s| s.ident.to_string())
61                .unwrap_or_default();
62            let (size, align) = primitive_size_align(&name, arch);
63            (size, align, TypeInfo::Primitive { name, size, align })
64        }
65        Type::Ptr(_) | Type::Reference(_) => {
66            let s = arch.pointer_size;
67            (s, s, TypeInfo::Pointer { size: s, align: s })
68        }
69        Type::Array(arr) => {
70            let (elem_size, elem_align, elem_ty) = rust_type_size_align(&arr.elem, arch);
71            let count = array_len_from_expr(&arr.len);
72            let size = elem_size * count;
73            (
74                size,
75                elem_align,
76                TypeInfo::Array {
77                    element: Box::new(elem_ty),
78                    count,
79                    size,
80                    align: elem_align,
81                },
82            )
83        }
84        _ => {
85            let s = arch.pointer_size;
86            (
87                s,
88                s,
89                TypeInfo::Opaque {
90                    name: "(unknown)".into(),
91                    size: s,
92                    align: s,
93                },
94            )
95        }
96    }
97}
98
99fn primitive_size_align(name: &str, arch: &'static ArchConfig) -> (usize, usize) {
100    match name {
101        "bool" | "u8" | "i8" => (1, 1),
102        "u16" | "i16" => (2, 2),
103        "u32" | "i32" | "f32" => (4, 4),
104        "u64" | "i64" | "f64" => (8, 8),
105        "u128" | "i128" => (16, 16),
106        "usize" | "isize" => (arch.pointer_size, arch.pointer_size),
107        "char" => (4, 4), // Rust char is a Unicode scalar (4 bytes)
108        // x86 SSE / AVX / AVX-512 SIMD types (via std::arch or target_feature)
109        "__m64" => (8, 8),
110        "__m128" | "__m128d" | "__m128i" => (16, 16),
111        "__m256" | "__m256d" | "__m256i" => (32, 32),
112        "__m512" | "__m512d" | "__m512i" => (64, 64),
113        // Rust portable SIMD / packed types (std::simd, packed_simd)
114        "f32x4" | "i32x4" | "u32x4" => (16, 16),
115        "f64x2" | "i64x2" | "u64x2" => (16, 16),
116        "f32x8" | "i32x8" | "u32x8" => (32, 32),
117        "f64x4" | "i64x4" | "u64x4" => (32, 32),
118        "f32x16" | "i32x16" | "u32x16" => (64, 64),
119        _ => (arch.pointer_size, arch.pointer_size),
120    }
121}
122
123fn array_len_from_expr(expr: &syn::Expr) -> usize {
124    if let syn::Expr::Lit(syn::ExprLit {
125        lit: syn::Lit::Int(n),
126        ..
127    }) = expr
128    {
129        n.base10_parse::<usize>().unwrap_or(0)
130    } else {
131        0
132    }
133}
134
135// ── struct repr detection ─────────────────────────────────────────────────────
136
137fn is_packed(attrs: &[syn::Attribute]) -> bool {
138    attrs
139        .iter()
140        .any(|a| a.path().is_ident("repr") && a.to_token_stream().to_string().contains("packed"))
141}
142
143fn simulate_rust_layout(
144    name: String,
145    fields: &[(String, Type)],
146    packed: bool,
147    arch: &'static ArchConfig,
148) -> StructLayout {
149    let mut offset = 0usize;
150    let mut struct_align = 1usize;
151    let mut out_fields: Vec<Field> = Vec::new();
152
153    for (fname, ty) in fields {
154        let (size, align, type_info) = rust_type_size_align(ty, arch);
155        let effective_align = if packed { 1 } else { align };
156
157        if effective_align > 0 {
158            offset = offset.next_multiple_of(effective_align);
159        }
160        struct_align = struct_align.max(effective_align);
161
162        out_fields.push(Field {
163            name: fname.clone(),
164            ty: type_info,
165            offset,
166            size,
167            align: effective_align,
168            source_file: None,
169            source_line: None,
170            access: AccessPattern::Unknown,
171        });
172        offset += size;
173    }
174
175    if !packed && struct_align > 0 {
176        offset = offset.next_multiple_of(struct_align);
177    }
178
179    StructLayout {
180        name,
181        total_size: offset,
182        align: struct_align,
183        fields: out_fields,
184        source_file: None,
185        source_line: None,
186        arch,
187        is_packed: packed,
188        is_union: false,
189    }
190}
191
192// ── visitor ───────────────────────────────────────────────────────────────────
193
194struct StructVisitor {
195    arch: &'static ArchConfig,
196    layouts: Vec<StructLayout>,
197}
198
199impl<'ast> Visit<'ast> for StructVisitor {
200    fn visit_item_struct(&mut self, node: &'ast ItemStruct) {
201        syn::visit::visit_item_struct(self, node); // recurse into nested items
202
203        let name = node.ident.to_string();
204        let packed = is_packed(&node.attrs);
205
206        // Collect (field_name, type, optional_guard)
207        let fields: Vec<(String, Type, Option<String>)> = match &node.fields {
208            Fields::Named(nf) => nf
209                .named
210                .iter()
211                .map(|f| {
212                    let fname = f.ident.as_ref().map(|i| i.to_string()).unwrap_or_default();
213                    let guard = extract_guard_from_attrs(&f.attrs);
214                    (fname, f.ty.clone(), guard)
215                })
216                .collect(),
217            Fields::Unnamed(uf) => uf
218                .unnamed
219                .iter()
220                .enumerate()
221                .map(|(i, f)| {
222                    let guard = extract_guard_from_attrs(&f.attrs);
223                    (format!("_{i}"), f.ty.clone(), guard)
224                })
225                .collect(),
226            Fields::Unit => vec![],
227        };
228
229        let name_ty: Vec<(String, Type)> = fields
230            .iter()
231            .map(|(n, t, _)| (n.clone(), t.clone()))
232            .collect();
233        let mut layout = simulate_rust_layout(name, &name_ty, packed, self.arch);
234        layout.source_line = Some(node.ident.span().start().line as u32);
235
236        // Apply explicit guard annotations; these take precedence over the
237        // heuristic type-name pass in concurrency.rs (which skips non-Unknown fields).
238        for (i, (_, _, guard)) in fields.iter().enumerate() {
239            if let Some(g) = guard {
240                layout.fields[i].access = AccessPattern::Concurrent {
241                    guard: Some(g.clone()),
242                    is_atomic: false,
243                };
244            }
245        }
246
247        self.layouts.push(layout);
248    }
249}
250
251// ── public API ────────────────────────────────────────────────────────────────
252
253pub fn parse_rust(source: &str, arch: &'static ArchConfig) -> anyhow::Result<Vec<StructLayout>> {
254    let file: syn::File = syn::parse_str(source)?;
255    let mut visitor = StructVisitor {
256        arch,
257        layouts: Vec::new(),
258    };
259    visitor.visit_file(&file);
260    Ok(visitor.layouts)
261}
262
263// ── tests ─────────────────────────────────────────────────────────────────────
264
265#[cfg(test)]
266mod tests {
267    use super::*;
268    use padlock_core::arch::X86_64_SYSV;
269
270    #[test]
271    fn parse_simple_struct() {
272        let src = "struct Foo { a: u8, b: u64, c: u32 }";
273        let layouts = parse_rust(src, &X86_64_SYSV).unwrap();
274        assert_eq!(layouts.len(), 1);
275        let l = &layouts[0];
276        assert_eq!(l.name, "Foo");
277        assert_eq!(l.fields.len(), 3);
278        assert_eq!(l.fields[0].size, 1); // u8
279        assert_eq!(l.fields[1].size, 8); // u64
280        assert_eq!(l.fields[2].size, 4); // u32
281    }
282
283    #[test]
284    fn layout_includes_padding() {
285        // u8 then u64: 7 bytes padding inserted
286        let src = "struct T { a: u8, b: u64 }";
287        let layouts = parse_rust(src, &X86_64_SYSV).unwrap();
288        let l = &layouts[0];
289        assert_eq!(l.fields[0].offset, 0);
290        assert_eq!(l.fields[1].offset, 8); // u64 aligned to 8
291        assert_eq!(l.total_size, 16);
292        let gaps = padlock_core::ir::find_padding(l);
293        assert_eq!(gaps[0].bytes, 7);
294    }
295
296    #[test]
297    fn multiple_structs_parsed() {
298        let src = "struct A { x: u32 } struct B { y: u64 }";
299        let layouts = parse_rust(src, &X86_64_SYSV).unwrap();
300        assert_eq!(layouts.len(), 2);
301    }
302
303    #[test]
304    fn packed_struct_no_padding() {
305        let src = "#[repr(packed)] struct P { a: u8, b: u64 }";
306        let layouts = parse_rust(src, &X86_64_SYSV).unwrap();
307        let l = &layouts[0];
308        assert!(l.is_packed);
309        assert_eq!(l.fields[1].offset, 1); // no padding, b immediately after a
310        let gaps = padlock_core::ir::find_padding(l);
311        assert!(gaps.is_empty());
312    }
313
314    #[test]
315    fn pointer_field_uses_arch_size() {
316        let src = "struct S { p: *const u8 }";
317        let layouts = parse_rust(src, &X86_64_SYSV).unwrap();
318        assert_eq!(layouts[0].fields[0].size, 8); // 64-bit pointer
319    }
320
321    // ── attribute guard extraction ─────────────────────────────────────────────
322
323    #[test]
324    fn lock_protected_by_attr_sets_guard() {
325        let src = r#"
326struct Cache {
327    #[lock_protected_by = "mu"]
328    readers: u64,
329    mu: u64,
330}
331"#;
332        let layouts = parse_rust(src, &X86_64_SYSV).unwrap();
333        let readers = &layouts[0].fields[0];
334        assert_eq!(readers.name, "readers");
335        if let AccessPattern::Concurrent { guard, .. } = &readers.access {
336            assert_eq!(guard.as_deref(), Some("mu"));
337        } else {
338            panic!("expected Concurrent, got {:?}", readers.access);
339        }
340    }
341
342    #[test]
343    fn guarded_by_string_attr_sets_guard() {
344        let src = r#"
345struct S {
346    #[guarded_by("lock")]
347    value: u32,
348}
349"#;
350        let layouts = parse_rust(src, &X86_64_SYSV).unwrap();
351        if let AccessPattern::Concurrent { guard, .. } = &layouts[0].fields[0].access {
352            assert_eq!(guard.as_deref(), Some("lock"));
353        } else {
354            panic!("expected Concurrent");
355        }
356    }
357
358    #[test]
359    fn guarded_by_ident_attr_sets_guard() {
360        let src = r#"
361struct S {
362    #[guarded_by(mu)]
363    count: u64,
364}
365"#;
366        let layouts = parse_rust(src, &X86_64_SYSV).unwrap();
367        if let AccessPattern::Concurrent { guard, .. } = &layouts[0].fields[0].access {
368            assert_eq!(guard.as_deref(), Some("mu"));
369        } else {
370            panic!("expected Concurrent");
371        }
372    }
373
374    #[test]
375    fn protected_by_attr_sets_guard() {
376        let src = r#"
377struct S {
378    #[protected_by = "lock_a"]
379    x: u64,
380}
381"#;
382        let layouts = parse_rust(src, &X86_64_SYSV).unwrap();
383        if let AccessPattern::Concurrent { guard, .. } = &layouts[0].fields[0].access {
384            assert_eq!(guard.as_deref(), Some("lock_a"));
385        } else {
386            panic!("expected Concurrent");
387        }
388    }
389
390    #[test]
391    fn different_guards_on_same_cache_line_is_false_sharing() {
392        // readers and writers are at offsets 0 and 8 — same cache line (line 0).
393        // They have different explicit guards → confirmed false sharing.
394        let src = r#"
395struct HotPath {
396    #[lock_protected_by = "mu_a"]
397    readers: u64,
398    #[lock_protected_by = "mu_b"]
399    writers: u64,
400}
401"#;
402        let layouts = parse_rust(src, &X86_64_SYSV).unwrap();
403        assert!(padlock_core::analysis::false_sharing::has_false_sharing(
404            &layouts[0]
405        ));
406    }
407
408    #[test]
409    fn same_guard_on_same_cache_line_is_not_false_sharing() {
410        let src = r#"
411struct Safe {
412    #[lock_protected_by = "mu"]
413    a: u64,
414    #[lock_protected_by = "mu"]
415    b: u64,
416}
417"#;
418        let layouts = parse_rust(src, &X86_64_SYSV).unwrap();
419        assert!(!padlock_core::analysis::false_sharing::has_false_sharing(
420            &layouts[0]
421        ));
422    }
423
424    #[test]
425    fn unannotated_field_stays_unknown() {
426        let src = "struct S { x: u64 }";
427        let layouts = parse_rust(src, &X86_64_SYSV).unwrap();
428        assert!(matches!(
429            layouts[0].fields[0].access,
430            AccessPattern::Unknown
431        ));
432    }
433}