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}