use super::super::{CertRule, RuleViolation};
use crate::manifest::{RuleCategory, Severity};
use crate::utility::cert_c::ast_utils::get_node_text;
use tree_sitter::Node;
pub struct Con35C;
impl CertRule for Con35C {
fn rule_id(&self) -> &'static str {
"CON35-C"
}
fn description(&self) -> &'static str {
"Avoid deadlock by locking in a predefined order"
}
fn severity(&self) -> Severity {
Severity::Low
}
fn category(&self) -> RuleCategory {
RuleCategory::Rule
}
fn cert_id(&self) -> &'static str {
"CON35-C"
}
fn check(&self, node: &Node, source: &str) -> Vec<RuleViolation> {
let mut violations = Vec::new();
violations.extend(self.check_multiple_locks(*node, source));
violations
}
}
impl Con35C {
fn check_multiple_locks(&self, node: Node, source: &str) -> Vec<RuleViolation> {
let mut violations = Vec::new();
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
if child.kind() == "function_definition" {
if let Some(violation) = self.analyze_function(child, source) {
violations.push(violation);
}
}
violations.extend(self.check_multiple_locks(child, source));
}
violations
}
fn analyze_function(&self, func_node: Node, source: &str) -> Option<RuleViolation> {
let func_name = self.get_function_name(func_node, source);
let lock_calls = self.find_lock_calls(func_node, source);
if lock_calls.len() < 2 {
return None;
}
let has_ordering = self.has_ordering_mechanism(func_node, source, &lock_calls);
if !has_ordering {
let first_lock = &lock_calls[0];
let violation_msg = format!(
"Function '{}' locks multiple mutexes without a predefined locking order. Found {} lock operations. This can lead to deadlock if different threads lock these mutexes in different orders",
func_name.unwrap_or_else(|| "<unknown>".to_string()),
lock_calls.len()
);
return Some(RuleViolation {
rule_id: self.rule_id().to_string(),
file_path: "".to_string(),
severity: self.severity(),
line: first_lock.start_position().row + 1,
column: first_lock.start_position().column + 1,
message: violation_msg,
suggestion: Some("Implement a predefined locking order (e.g., by comparing mutex IDs before locking)".to_string()),
requires_manual_review: Some(false),
});
}
None
}
fn get_function_name(&self, func_node: Node, source: &str) -> Option<String> {
let mut cursor = func_node.walk();
for child in func_node.children(&mut cursor) {
if child.kind() == "function_declarator" {
let mut decl_cursor = child.walk();
for decl_child in child.children(&mut decl_cursor) {
if decl_child.kind() == "identifier" {
return Some(get_node_text(&decl_child, source).to_string());
}
}
}
}
None
}
fn find_lock_calls<'a>(&self, func_node: Node<'a>, source: &str) -> Vec<Node<'a>> {
let mut lock_calls = Vec::new();
self.find_lock_calls_recursive(func_node, source, &mut lock_calls);
lock_calls
}
fn find_lock_calls_recursive<'a>(
&self,
node: Node<'a>,
source: &str,
lock_calls: &mut Vec<Node<'a>>,
) {
if node.kind() == "call_expression" {
if let Some(func_name_node) = node.child_by_field_name("function") {
let func_name = get_node_text(&func_name_node, source);
if func_name == "mtx_lock" || func_name == "pthread_mutex_lock" {
lock_calls.push(node);
}
}
}
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
self.find_lock_calls_recursive(child, source, lock_calls);
}
}
fn has_ordering_mechanism(&self, func_node: Node, source: &str, lock_calls: &[Node]) -> bool {
if self.has_conditional_lock_order(func_node, source, lock_calls) {
return true;
}
if self.has_comparison_based_ordering(func_node, source) {
return true;
}
false
}
fn has_conditional_lock_order(
&self,
_func_node: Node,
source: &str,
lock_calls: &[Node],
) -> bool {
for lock_call in lock_calls {
if let Some(args_node) = lock_call.child_by_field_name("arguments") {
let arg_text = get_node_text(&args_node, source);
if arg_text.contains("first")
|| arg_text.contains("second")
|| arg_text.contains("lock1")
|| arg_text.contains("lock2")
|| arg_text.contains("ordered")
{
return true;
}
}
}
false
}
fn has_comparison_based_ordering(&self, func_node: Node, source: &str) -> bool {
self.has_comparison_recursive(func_node, source)
}
fn has_comparison_recursive(&self, node: Node, source: &str) -> bool {
if node.kind() == "binary_expression" {
let operator = node.child(1).map(|n| get_node_text(&n, source));
if let Some(op) = operator {
if op == "<" || op == ">" || op == "<=" || op == ">=" {
let full_text = get_node_text(&node, source);
if full_text.contains("->id") || full_text.contains(".id") {
return true;
}
}
}
}
if node.kind() == "if_statement" {
if let Some(condition) = node.child_by_field_name("condition") {
let condition_text = get_node_text(&condition, source);
if (condition_text.contains("->id") || condition_text.contains(".id"))
&& (condition_text.contains('<') || condition_text.contains('>'))
{
return true;
}
}
}
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
if self.has_comparison_recursive(child, source) {
return true;
}
}
false
}
}