Skip to main content

padlock_source/
concurrency.rs

1// padlock-source/src/concurrency.rs
2//
3// Heuristic pass: annotate field AccessPatterns based on known concurrency
4// type names in Rust, C++, and Go.
5
6use padlock_core::ir::{AccessPattern, StructLayout};
7
8use crate::SourceLanguage;
9
10/// Update `AccessPattern` for fields whose type names suggest concurrent access.
11///
12/// This is a best-effort heuristic: it matches well-known synchronisation
13/// wrapper names (`Mutex<T>`, `std::atomic<T>`, `sync.Mutex`, etc.) found in
14/// the `TypeInfo` name of each field.
15pub fn annotate_concurrency(layout: &mut StructLayout, language: &SourceLanguage) {
16    for field in &mut layout.fields {
17        let ty_name = match &field.ty {
18            padlock_core::ir::TypeInfo::Primitive { name, .. }
19            | padlock_core::ir::TypeInfo::Opaque { name, .. } => name.clone(),
20            _ => continue,
21        };
22
23        if is_concurrent_type(&ty_name, language) {
24            let is_atomic = is_atomic_type(&ty_name, language);
25            if matches!(field.access, AccessPattern::Unknown) {
26                // Use the field name as the guard so that two concurrent fields
27                // with different names on the same cache line are flagged as
28                // false-sharing candidates (different guards → different data).
29                field.access = AccessPattern::Concurrent {
30                    guard: Some(field.name.clone()),
31                    is_atomic,
32                    is_annotated: false,
33                };
34            }
35        } else if is_read_mostly_type(&ty_name, language)
36            && matches!(field.access, AccessPattern::Unknown)
37        {
38            field.access = AccessPattern::ReadMostly;
39        }
40    }
41}
42
43/// Annotate fields whose type name contains any of `custom_types` as Concurrent.
44///
45/// Called after the built-in heuristic pass to extend detection to project-specific
46/// synchronization wrappers (e.g. `SeqLock`, `TicketLock`) registered in
47/// `.padlock.toml` under `[padlock] custom_sync_types`.
48pub fn annotate_custom_types(layout: &mut StructLayout, custom_types: &[String]) {
49    if custom_types.is_empty() {
50        return;
51    }
52    for field in &mut layout.fields {
53        if !matches!(field.access, AccessPattern::Unknown) {
54            continue; // already annotated by built-in pass or explicit guard
55        }
56        let ty_name = match &field.ty {
57            padlock_core::ir::TypeInfo::Primitive { name, .. }
58            | padlock_core::ir::TypeInfo::Opaque { name, .. } => name.clone(),
59            _ => continue,
60        };
61        if custom_types.iter().any(|ct| ty_name.contains(ct.as_str())) {
62            field.access = AccessPattern::Concurrent {
63                guard: Some(field.name.clone()),
64                is_atomic: false,
65                is_annotated: false,
66            };
67        }
68    }
69}
70
71/// Returns `true` if any field has a `Concurrent` access pattern.
72pub fn has_concurrent_fields(layout: &StructLayout) -> bool {
73    layout
74        .fields
75        .iter()
76        .any(|f| matches!(f.access, AccessPattern::Concurrent { .. }))
77}
78
79fn is_concurrent_type(name: &str, lang: &SourceLanguage) -> bool {
80    match lang {
81        SourceLanguage::Rust => {
82            name.starts_with("Mutex")
83                || name.starts_with("RwLock")
84                || name.starts_with("Arc")
85                || name.contains("Atomic")
86                || name.starts_with("Condvar")
87                || name.starts_with("Once")
88        }
89        SourceLanguage::C | SourceLanguage::Cpp => {
90            name.contains("mutex")
91                || name.contains("atomic")
92                || name.contains("spinlock")
93                || name.contains("critical_section")
94                || name.contains("pthread_mutex")
95        }
96        SourceLanguage::Go => {
97            name == "sync.Mutex"
98                || name == "sync.RWMutex"
99                || name == "Mutex"
100                || name == "RWMutex"
101                || name.contains("atomic")
102        }
103        SourceLanguage::Zig => {
104            name.contains("Mutex")
105                || name.contains("RwLock")
106                || name.contains("atomic.Value")
107                || name.contains("Atomic")
108        }
109    }
110}
111
112fn is_atomic_type(name: &str, lang: &SourceLanguage) -> bool {
113    match lang {
114        SourceLanguage::Rust => name.contains("Atomic"),
115        SourceLanguage::C | SourceLanguage::Cpp => name.contains("atomic"),
116        SourceLanguage::Go => name.contains("atomic"),
117        SourceLanguage::Zig => name.contains("atomic.Value") || name.contains("Atomic"),
118    }
119}
120
121fn is_read_mostly_type(name: &str, lang: &SourceLanguage) -> bool {
122    match lang {
123        SourceLanguage::Rust => name.starts_with("RwLock"),
124        SourceLanguage::C | SourceLanguage::Cpp => {
125            name.contains("rwlock") || name.contains("shared_mutex")
126        }
127        SourceLanguage::Go => name == "sync.RWMutex" || name == "RWMutex",
128        SourceLanguage::Zig => name.contains("RwLock"),
129    }
130}
131
132// ── tests ─────────────────────────────────────────────────────────────────────
133
134#[cfg(test)]
135mod tests {
136    use super::*;
137    use padlock_core::arch::X86_64_SYSV;
138    use padlock_core::ir::{AccessPattern, Field, StructLayout, TypeInfo};
139
140    fn field_with_type(name: &str, ty_name: &str) -> Field {
141        Field {
142            name: name.into(),
143            ty: TypeInfo::Primitive {
144                name: ty_name.into(),
145                size: 8,
146                align: 8,
147            },
148            offset: 0,
149            size: 8,
150            align: 8,
151            source_file: None,
152            source_line: None,
153            access: AccessPattern::Unknown,
154        }
155    }
156
157    fn layout_with_fields(fields: Vec<Field>) -> StructLayout {
158        StructLayout {
159            name: "T".into(),
160            total_size: 64,
161            align: 8,
162            fields,
163            source_file: None,
164            source_line: None,
165            arch: &X86_64_SYSV,
166            is_packed: false,
167            is_union: false,
168            is_repr_rust: false,
169            suppressed_findings: Vec::new(),
170            uncertain_fields: Vec::new(),
171        }
172    }
173
174    #[test]
175    fn rust_mutex_field_is_annotated() {
176        let mut layout = layout_with_fields(vec![field_with_type("counter", "Mutex")]);
177        annotate_concurrency(&mut layout, &SourceLanguage::Rust);
178        assert!(matches!(
179            layout.fields[0].access,
180            AccessPattern::Concurrent { .. }
181        ));
182    }
183
184    #[test]
185    fn rust_atomic_is_atomic() {
186        let mut layout = layout_with_fields(vec![field_with_type("count", "AtomicU64")]);
187        annotate_concurrency(&mut layout, &SourceLanguage::Rust);
188        if let AccessPattern::Concurrent { is_atomic, .. } = &layout.fields[0].access {
189            assert!(is_atomic);
190        } else {
191            panic!("expected Concurrent");
192        }
193    }
194
195    #[test]
196    fn cpp_mutex_annotated() {
197        let mut layout = layout_with_fields(vec![field_with_type("mu", "std::mutex")]);
198        annotate_concurrency(&mut layout, &SourceLanguage::Cpp);
199        assert!(has_concurrent_fields(&layout));
200    }
201
202    #[test]
203    fn unknown_field_stays_unknown() {
204        let mut layout = layout_with_fields(vec![field_with_type("x", "int")]);
205        annotate_concurrency(&mut layout, &SourceLanguage::C);
206        assert!(matches!(layout.fields[0].access, AccessPattern::Unknown));
207    }
208
209    #[test]
210    fn has_concurrent_fields_false_when_none() {
211        let layout = layout_with_fields(vec![field_with_type("x", "int")]);
212        assert!(!has_concurrent_fields(&layout));
213    }
214}