Skip to main content

decy_verify/
lock_verify.rs

1//! Lock discipline verification (DECY-079).
2//!
3//! Validates that all accesses to shared data are properly protected by locks
4//! and detects potential deadlocks.
5//!
6//! # Overview
7//!
8//! This module provides comprehensive lock discipline checking for C code that
9//! uses pthread mutexes. It detects two major categories of concurrency bugs:
10//!
11//! 1. **Unprotected Data Access**: Accessing shared data outside locked regions
12//! 2. **Deadlock Risk**: Inconsistent lock ordering across functions
13//!
14//! # Example
15//!
16//! ```no_run
17//! use decy_analyzer::lock_analysis::LockAnalyzer;
18//! use decy_verify::lock_verify::LockDisciplineChecker;
19//! use decy_hir::HirFunction;
20//!
21//! let analyzer = LockAnalyzer::new();
22//! let checker = LockDisciplineChecker::new(&analyzer);
23//!
24//! // Check single function for unprotected access
25//! # let func = HirFunction::new("test".to_string(), decy_hir::HirType::Void, vec![]);
26//! let violations = checker.check_unprotected_access(&func);
27//!
28//! // Check multiple functions for deadlock risk
29//! let warnings = checker.check_deadlock_risk(&[func]);
30//! ```
31//!
32//! # Algorithm
33//!
34//! ## Unprotected Access Detection
35//!
36//! 1. Use `LockAnalyzer` to identify which variables are protected by locks
37//! 2. Find all locked regions in the function
38//! 3. For each statement outside locked regions, check if it accesses protected data
39//! 4. Report violations with statement numbers
40//!
41//! ## Deadlock Detection
42//!
43//! 1. Extract lock acquisition order from each function
44//! 2. Compare orderings pairwise
45//! 3. Detect reverse orderings (e.g., A→B vs B→A) which indicate deadlock risk
46//! 4. Report potential deadlocks with involved locks
47
48use decy_analyzer::lock_analysis::LockAnalyzer;
49use decy_hir::HirFunction;
50
51/// Comprehensive lock discipline report.
52///
53/// Summarizes all lock discipline violations found in a function or set of functions.
54#[derive(Debug, Clone, PartialEq, Eq)]
55pub struct LockDisciplineReport {
56    /// Number of unprotected data accesses detected.
57    ///
58    /// This counts instances where shared data (identified by lock analysis)
59    /// is accessed outside of any locked region.
60    pub unprotected_accesses: usize,
61
62    /// Number of lock/unlock violations.
63    ///
64    /// This includes:
65    /// - Locks without corresponding unlocks
66    /// - Unlocks without corresponding locks
67    pub lock_violations: usize,
68
69    /// Number of deadlock warnings.
70    ///
71    /// This counts potential deadlocks from inconsistent lock ordering.
72    pub deadlock_warnings: usize,
73}
74
75impl LockDisciplineReport {
76    /// Check if the code has no lock discipline violations.
77    ///
78    /// Returns `true` only if all counts are zero.
79    pub fn is_clean(&self) -> bool {
80        self.unprotected_accesses == 0 && self.lock_violations == 0 && self.deadlock_warnings == 0
81    }
82}
83
84/// Lock discipline checker.
85///
86/// Validates lock discipline for pthread mutex usage in C code.
87pub struct LockDisciplineChecker<'a> {
88    /// Reference to the lock analyzer for identifying protected data
89    analyzer: &'a LockAnalyzer,
90}
91
92impl<'a> LockDisciplineChecker<'a> {
93    /// Create a new lock discipline checker.
94    ///
95    /// # Arguments
96    ///
97    /// * `analyzer` - Lock analyzer for identifying protected data and lock regions
98    pub fn new(analyzer: &'a LockAnalyzer) -> Self {
99        Self { analyzer }
100    }
101
102    /// Check for unprotected data accesses.
103    ///
104    /// Detects when shared data (identified by lock analysis) is accessed
105    /// outside of locked regions.
106    ///
107    /// # Arguments
108    ///
109    /// * `func` - Function to check
110    ///
111    /// # Returns
112    ///
113    /// Vector of violation messages, each describing:
114    /// - The variable name accessed
115    /// - The statement number where the violation occurs
116    ///
117    /// # Example
118    ///
119    /// ```no_run
120    /// # use decy_analyzer::lock_analysis::LockAnalyzer;
121    /// # use decy_verify::lock_verify::LockDisciplineChecker;
122    /// # use decy_hir::HirFunction;
123    /// # let analyzer = LockAnalyzer::new();
124    /// # let func = HirFunction::new("test".to_string(), decy_hir::HirType::Void, vec![]);
125    /// let checker = LockDisciplineChecker::new(&analyzer);
126    /// let violations = checker.check_unprotected_access(&func);
127    ///
128    /// for violation in violations {
129    ///     println!("Lock violation: {}", violation);
130    /// }
131    /// ```
132    pub fn check_unprotected_access(&self, func: &HirFunction) -> Vec<String> {
133        let mut violations = Vec::new();
134
135        // 1. Identify protected data from lock analysis
136        let mapping = self.analyzer.analyze_lock_data_mapping(func);
137        let protected_vars: std::collections::HashSet<String> =
138            mapping.get_locks().iter().flat_map(|lock| mapping.get_protected_data(lock)).collect();
139
140        // 2. Find lock regions
141        let lock_regions = self.analyzer.find_lock_regions(func);
142
143        // 3. Check all statements outside locked regions
144        let body = func.body();
145        for (idx, stmt) in body.iter().enumerate() {
146            // Skip if this statement is inside a locked region
147            if self.is_inside_any_region(idx, &lock_regions) {
148                continue;
149            }
150
151            // Check if statement accesses protected data
152            let accessed_vars = self.collect_accessed_vars(stmt);
153            for var in accessed_vars {
154                if protected_vars.contains(&var) {
155                    violations.push(format!(
156                        "Unprotected access to '{}' at statement {} (outside locked region)",
157                        var, idx
158                    ));
159                }
160            }
161        }
162
163        violations
164    }
165
166    /// Check if a statement index is inside any lock region
167    fn is_inside_any_region(
168        &self,
169        idx: usize,
170        regions: &[decy_analyzer::lock_analysis::LockRegion],
171    ) -> bool {
172        regions.iter().any(|r| idx > r.start_index && idx < r.end_index)
173    }
174
175    /// Collect all variable names accessed in a statement
176    fn collect_accessed_vars(&self, stmt: &decy_hir::HirStatement) -> Vec<String> {
177        use decy_hir::HirStatement;
178        let mut vars = Vec::new();
179
180        match stmt {
181            HirStatement::Assignment { target, value } => {
182                vars.push(target.clone());
183                Self::collect_vars_from_expr(value, &mut vars);
184            }
185            HirStatement::VariableDeclaration { initializer: Some(init), .. } => {
186                Self::collect_vars_from_expr(init, &mut vars);
187            }
188            HirStatement::Expression(expr) => {
189                Self::collect_vars_from_expr(expr, &mut vars);
190            }
191            HirStatement::Return(Some(expr)) => {
192                Self::collect_vars_from_expr(expr, &mut vars);
193            }
194            _ => {}
195        }
196
197        vars
198    }
199
200    /// Recursively collect variable names from an expression
201    fn collect_vars_from_expr(expr: &decy_hir::HirExpression, vars: &mut Vec<String>) {
202        use decy_hir::HirExpression;
203
204        match expr {
205            HirExpression::Variable(name) => {
206                vars.push(name.clone());
207            }
208            HirExpression::BinaryOp { left, right, .. } => {
209                Self::collect_vars_from_expr(left, vars);
210                Self::collect_vars_from_expr(right, vars);
211            }
212            HirExpression::UnaryOp { operand, .. } => {
213                Self::collect_vars_from_expr(operand, vars);
214            }
215            HirExpression::FunctionCall { arguments, .. } => {
216                for arg in arguments {
217                    Self::collect_vars_from_expr(arg, vars);
218                }
219            }
220            HirExpression::AddressOf(inner) | HirExpression::Dereference(inner) => {
221                Self::collect_vars_from_expr(inner, vars);
222            }
223            HirExpression::ArrayIndex { array, index } => {
224                Self::collect_vars_from_expr(array, vars);
225                Self::collect_vars_from_expr(index, vars);
226            }
227            HirExpression::FieldAccess { object, .. } => {
228                Self::collect_vars_from_expr(object, vars);
229            }
230            HirExpression::Cast { expr, .. } => {
231                Self::collect_vars_from_expr(expr, vars);
232            }
233            _ => {}
234        }
235    }
236
237    /// Check for potential deadlocks
238    ///
239    /// Analyzes lock ordering across multiple functions to detect
240    /// inconsistent lock acquisition patterns that could cause deadlocks.
241    pub fn check_deadlock_risk(&self, functions: &[HirFunction]) -> Vec<String> {
242        let mut warnings = Vec::new();
243
244        // Edge case: single lock or no locks can't deadlock
245        if functions.is_empty() {
246            return warnings;
247        }
248
249        // 1. Extract lock ordering for each function
250        let mut lock_orderings: Vec<Vec<String>> = Vec::new();
251        for func in functions {
252            let ordering = self.extract_lock_ordering(func);
253            if !ordering.is_empty() {
254                lock_orderings.push(ordering);
255            }
256        }
257
258        // 2. Check for inconsistent orderings
259        for i in 0..lock_orderings.len() {
260            for j in (i + 1)..lock_orderings.len() {
261                if let Some(warning) =
262                    self.detect_ordering_conflict(&lock_orderings[i], &lock_orderings[j])
263                {
264                    warnings.push(warning);
265                }
266            }
267        }
268
269        warnings
270    }
271
272    /// Extract the lock acquisition order from a function
273    fn extract_lock_ordering(&self, func: &HirFunction) -> Vec<String> {
274        use decy_hir::{HirExpression, HirStatement};
275        let mut ordering = Vec::new();
276        let body = func.body();
277
278        for stmt in body {
279            if let HirStatement::Expression(HirExpression::FunctionCall { function, arguments }) =
280                stmt
281            {
282                if function == "pthread_mutex_lock" {
283                    if let Some(HirExpression::AddressOf(inner)) = arguments.first() {
284                        if let HirExpression::Variable(name) = &**inner {
285                            ordering.push(name.clone());
286                        }
287                    }
288                }
289            }
290        }
291
292        ordering
293    }
294
295    /// Detect if two lock orderings conflict (could cause deadlock)
296    fn detect_ordering_conflict(
297        &self,
298        ordering1: &[String],
299        ordering2: &[String],
300    ) -> Option<String> {
301        // For each pair of locks in ordering1, check if they appear in reverse order in ordering2
302        for i in 0..ordering1.len() {
303            for j in (i + 1)..ordering1.len() {
304                let lock_a = &ordering1[i];
305                let lock_b = &ordering1[j];
306
307                // Check if ordering2 has lock_b before lock_a
308                let pos_a_in_2 = ordering2.iter().position(|l| l == lock_a);
309                let pos_b_in_2 = ordering2.iter().position(|l| l == lock_b);
310
311                if let (Some(pos_a), Some(pos_b)) = (pos_a_in_2, pos_b_in_2) {
312                    if pos_b < pos_a {
313                        // Found reverse ordering - potential deadlock!
314                        return Some(format!(
315                            "Potential deadlock: Inconsistent lock ordering detected. \
316                             One function acquires {} then {}, another acquires {} then {}",
317                            lock_a, lock_b, lock_b, lock_a
318                        ));
319                    }
320                }
321            }
322        }
323
324        None
325    }
326
327    /// Comprehensive lock discipline check
328    ///
329    /// Runs all lock discipline checks and returns a summary report.
330    pub fn check_all(&self, func: &HirFunction) -> LockDisciplineReport {
331        let unprotected = self.check_unprotected_access(func);
332        let lock_violations = self.analyzer.check_lock_discipline(func);
333
334        LockDisciplineReport {
335            unprotected_accesses: unprotected.len(),
336            lock_violations: lock_violations.len(),
337            deadlock_warnings: 0, // Single function can't have cross-function deadlocks
338        }
339    }
340}