use crate::manifest::{RuleCategory, Severity};
use crate::prelude::RuleViolation;
use crate::rules::cert_c::CertRule;
use crate::utility::cert_c::ast_utils::get_node_text;
use std::collections::{HashMap, HashSet};
use tree_sitter::Node;
pub struct Pos53C;
impl CertRule for Pos53C {
fn rule_id(&self) -> &'static str {
"POS53-C"
}
fn cert_id(&self) -> &'static str {
"POS53"
}
fn description(&self) -> &'static str {
"Do not use more than one mutex for concurrent waiting operations on a condition variable"
}
fn severity(&self) -> Severity {
Severity::Medium
}
fn category(&self) -> RuleCategory {
RuleCategory::Rule
}
fn check(&self, node: &Node, source: &str) -> Vec<RuleViolation> {
let mut violations = Vec::new();
let mut cond_var_to_mutexes: HashMap<String, HashSet<String>> = HashMap::new();
let mut cond_var_locations: HashMap<String, Vec<(usize, usize)>> = HashMap::new();
self.collect_cond_wait_calls(
node,
source,
&mut cond_var_to_mutexes,
&mut cond_var_locations,
);
for (cond_var, mutexes) in &cond_var_to_mutexes {
if mutexes.len() > 1 {
if let Some(locations) = cond_var_locations.get(cond_var) {
if let Some(&(row, column)) = locations.first() {
let mutex_list: Vec<&String> = mutexes.iter().collect();
violations.push(RuleViolation {
rule_id: self.rule_id().to_string(),
severity: Severity::Medium,
message: format!(
"Condition variable '{}' is used with multiple different mutexes: {}. \
All threads waiting on a condition variable must use the same mutex.",
cond_var,
mutex_list
.iter()
.map(|s| format!("'{}'", s))
.collect::<Vec<_>>()
.join(", ")
),
file_path: String::new(),
line: row + 1,
column: column + 1,
suggestion: Some(
"Use the same mutex for all pthread_cond_wait() and pthread_cond_timedwait() calls on this condition variable, or use separate condition variables for different mutexes."
.to_string(),
),
..Default::default()
});
}
}
}
}
violations
}
}
impl Pos53C {
fn collect_cond_wait_calls(
&self,
node: &Node,
source: &str,
cond_var_to_mutexes: &mut HashMap<String, HashSet<String>>,
cond_var_locations: &mut HashMap<String, Vec<(usize, usize)>>,
) {
if node.kind() == "call_expression" {
if let Some(function) = node.child_by_field_name("function") {
let func_name = get_node_text(&function, source).trim();
if func_name == "pthread_cond_wait" || func_name == "pthread_cond_timedwait" {
if let Some(arguments) = node.child_by_field_name("arguments") {
let args = self.extract_arguments(&arguments, source);
if args.len() >= 2 {
let cond_var = get_node_text(&args[0], source).trim().to_string();
let mutex = get_node_text(&args[1], source).trim().to_string();
cond_var_to_mutexes
.entry(cond_var.clone())
.or_default()
.insert(mutex);
let start_point = node.start_position();
cond_var_locations
.entry(cond_var)
.or_default()
.push((start_point.row, start_point.column));
}
}
}
}
}
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
self.collect_cond_wait_calls(&child, source, cond_var_to_mutexes, cond_var_locations);
}
}
fn extract_arguments<'a>(&self, arguments: &'a Node, _source: &str) -> Vec<Node<'a>> {
let mut args = Vec::new();
let mut cursor = arguments.walk();
for child in arguments.children(&mut cursor) {
if child.kind() != "(" && child.kind() != ")" && child.kind() != "," {
args.push(child);
}
}
args
}
}