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 lang_parsing_substrate::query;
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 global_cond_vars = self.collect_global_cond_var_names(node, source);
let functions = query::find_descendants_of_kinds(*node, &["function_definition"]);
if functions.is_empty() {
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,
);
self.report_violations(&cond_var_to_mutexes, &cond_var_locations, &mut violations);
} else {
let mut global_cond_var_to_mutexes: HashMap<String, HashSet<String>> = HashMap::new();
let mut global_cond_var_locations: HashMap<String, Vec<(usize, usize)>> =
HashMap::new();
for func in &functions {
let mut local_cond_var_to_mutexes: HashMap<String, HashSet<String>> =
HashMap::new();
let mut local_cond_var_locations: HashMap<String, Vec<(usize, usize)>> =
HashMap::new();
self.collect_cond_wait_calls(
func,
source,
&mut local_cond_var_to_mutexes,
&mut local_cond_var_locations,
);
for (cond_var, mutexes) in local_cond_var_to_mutexes {
let locations = local_cond_var_locations
.remove(&cond_var)
.unwrap_or_default();
if global_cond_vars.contains(&cond_var) {
global_cond_var_to_mutexes
.entry(cond_var.clone())
.or_default()
.extend(mutexes);
global_cond_var_locations
.entry(cond_var)
.or_default()
.extend(locations);
} else {
let mut this_fn_map = HashMap::new();
this_fn_map.insert(cond_var.clone(), mutexes);
let mut this_fn_locations = HashMap::new();
this_fn_locations.insert(cond_var, locations);
self.report_violations(&this_fn_map, &this_fn_locations, &mut violations);
}
}
}
self.report_violations(
&global_cond_var_to_mutexes,
&global_cond_var_locations,
&mut violations,
);
}
violations
}
}
impl Pos53C {
fn report_violations(
&self,
cond_var_to_mutexes: &HashMap<String, HashSet<String>>,
cond_var_locations: &HashMap<String, Vec<(usize, usize)>>,
violations: &mut Vec<RuleViolation>,
) {
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()
});
}
}
}
}
}
fn collect_global_cond_var_names(&self, node: &Node, source: &str) -> HashSet<String> {
let mut names = HashSet::new();
for i in 0..node.child_count() {
if let Some(child) = node.child(i) {
if child.kind() == "declaration" {
let has_cond_t = query::find_descendants_of_kind(child, "type_identifier")
.iter()
.any(|t| get_node_text(t, source).trim() == "pthread_cond_t");
if !has_cond_t {
continue;
}
for name in self.collect_declared_names(&child, source) {
names.insert(format!("&{}", name));
names.insert(name);
}
}
}
}
names
}
fn collect_declared_names(&self, decl: &Node, source: &str) -> Vec<String> {
let mut names = Vec::new();
for i in 0..decl.child_count() {
if let Some(child) = decl.child(i) {
match child.kind() {
"identifier" => {
names.push(get_node_text(&child, source).trim().to_string());
}
"init_declarator" => {
if let Some(declarator) = child.child_by_field_name("declarator") {
if declarator.kind() == "identifier" {
names.push(get_node_text(&declarator, source).trim().to_string());
}
}
}
_ => {}
}
}
}
names
}
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)>>,
) {
for n in query::find_descendants_of_kind(*node, "call_expression") {
if let Some(function) = n.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) = n.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 = n.start_position();
cond_var_locations
.entry(cond_var)
.or_default()
.push((start_point.row, start_point.column));
}
}
}
}
}
}
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
}
}