use super::super::{CertRule, RuleViolation};
use crate::manifest::{RuleCategory, Severity};
use crate::utility::cert_c::ast_utils::get_node_text;
use lang_parsing_substrate::query;
use std::collections::HashMap;
use tree_sitter::Node;
pub struct Con40C;
impl Con40C {
#[allow(dead_code)]
pub fn new() -> Self {
Con40C
}
fn check_node<'a>(
&self,
node: &Node<'a>,
source: &'a str,
violations: &mut Vec<RuleViolation>,
) {
let mut atomic_vars = HashMap::new();
self.collect_atomic_vars(node, source, &mut atomic_vars);
self.check_expressions(node, source, &atomic_vars, violations);
self.check_load_modify_store(node, source, &atomic_vars, violations);
}
fn collect_atomic_vars<'a>(
&self,
node: &Node<'a>,
source: &'a str,
atomic_vars: &mut HashMap<String, bool>,
) {
for decl in query::find_descendants_of_kind(*node, "declaration") {
if let Some(type_node) = decl.child_by_field_name("type") {
let type_text = get_node_text(&type_node, source);
if type_text.contains("atomic_") || type_text.contains("_Atomic") {
for i in 0..decl.child_count() {
if let Some(child) = decl.child(i) {
if child.kind() == "init_declarator" || child.kind() == "identifier" {
if let Some(id) = self.get_identifier(&child, source) {
atomic_vars.insert(id.to_string(), true);
}
}
}
}
}
}
}
}
#[allow(clippy::only_used_in_recursion)]
fn get_identifier<'a>(&self, node: &Node<'a>, source: &'a str) -> Option<&'a str> {
if node.kind() == "identifier" {
return Some(get_node_text(node, source));
}
if node.kind() == "init_declarator" {
if let Some(declarator) = node.child_by_field_name("declarator") {
return self.get_identifier(&declarator, source);
}
}
for i in 0..node.child_count() {
if let Some(child) = node.child(i) {
if child.kind() == "identifier" {
return Some(get_node_text(&child, source));
}
if let Some(id) = self.get_identifier(&child, source) {
return Some(id);
}
}
}
None
}
fn check_expressions<'a>(
&self,
node: &Node<'a>,
source: &'a str,
atomic_vars: &HashMap<String, bool>,
violations: &mut Vec<RuleViolation>,
) {
let expr_kinds = [
"binary_expression",
"assignment_expression",
"call_expression",
"conditional_expression",
"unary_expression",
"parenthesized_expression",
];
for expr in query::find_descendants_of_kinds(*node, &expr_kinds) {
let mut var_counts: HashMap<String, Vec<Node>> = HashMap::new();
self.count_var_references(&expr, source, atomic_vars, &mut var_counts);
for (var_name, refs) in &var_counts {
if refs.len() >= 2 {
if !self.is_safe_compound_assignment(&expr, source, var_name) {
violations.push(RuleViolation {
rule_id: self.rule_id().to_string(),
line: expr.start_position().row + 1,
column: expr.start_position().column + 1,
message: format!(
"Atomic variable '{}' referenced {} times in single expression - creates race condition",
var_name, refs.len()
),
severity: self.severity(),
file_path: String::new(),
suggestion: None,
requires_manual_review: None,
});
}
}
}
}
}
#[allow(clippy::only_used_in_recursion)]
fn count_var_references<'a>(
&self,
node: &Node<'a>,
source: &'a str,
atomic_vars: &HashMap<String, bool>,
var_counts: &mut HashMap<String, Vec<Node<'a>>>,
) {
if node.kind() == "identifier" {
let var_name = get_node_text(node, source);
if atomic_vars.contains_key(var_name) {
var_counts
.entry(var_name.to_string())
.or_default()
.push(*node);
}
}
if node.kind() == "call_expression" {
return;
}
for i in 0..node.child_count() {
if let Some(child) = node.child(i) {
self.count_var_references(&child, source, atomic_vars, var_counts);
}
}
}
fn is_safe_compound_assignment(&self, node: &Node, source: &str, var_name: &str) -> bool {
let is_compound_assignment_on_var = |n: &Node| -> bool {
if n.kind() != "assignment_expression" {
return false;
}
let Some(op) = n.child_by_field_name("operator") else {
return false;
};
if get_node_text(&op, source) == "=" {
return false;
}
n.child_by_field_name("left")
.map(|left| get_node_text(&left, source) == var_name)
.unwrap_or(false)
};
is_compound_assignment_on_var(node)
|| query::find_ancestor(*node, |n| is_compound_assignment_on_var(&n)).is_some()
}
fn check_load_modify_store<'a>(
&self,
node: &Node<'a>,
source: &'a str,
atomic_vars: &HashMap<String, bool>,
violations: &mut Vec<RuleViolation>,
) {
for func in query::find_descendants_of_kind(*node, "function_definition") {
let Some(body) = func.child_by_field_name("body") else {
continue;
};
let mut loads: HashMap<String, Node> = HashMap::new();
let mut stores: HashMap<String, Node> = HashMap::new();
self.collect_atomic_operations(&body, source, atomic_vars, &mut loads, &mut stores);
for (var_name, load_node) in &loads {
if stores.contains_key(var_name) {
violations.push(RuleViolation {
rule_id: self.rule_id().to_string(),
line: load_node.start_position().row + 1,
column: load_node.start_position().column + 1,
message: format!(
"Non-atomic load-modify-store pattern detected on atomic variable '{}' - use atomic operations or mutex protection",
var_name
),
severity: self.severity(),
file_path: String::new(),
suggestion: Some("Consider using atomic_fetch_* operations or wrap with mutex locks".to_string()),
requires_manual_review: None,
});
}
}
}
}
fn collect_atomic_operations<'a>(
&self,
node: &Node<'a>,
source: &'a str,
atomic_vars: &HashMap<String, bool>,
loads: &mut HashMap<String, Node<'a>>,
stores: &mut HashMap<String, Node<'a>>,
) {
for call in query::find_descendants_of_kind(*node, "call_expression") {
if let Some(func_node) = call.child_by_field_name("function") {
let func_name = get_node_text(&func_node, source);
if func_name == "atomic_load" {
if let Some(args) = call.child_by_field_name("arguments") {
if let Some(var_name) =
self.extract_atomic_var_from_args(&args, source, atomic_vars)
{
loads.insert(var_name.to_string(), call);
}
}
}
if func_name == "atomic_store" {
if let Some(args) = call.child_by_field_name("arguments") {
if let Some(var_name) =
self.extract_atomic_var_from_args(&args, source, atomic_vars)
{
stores.insert(var_name.to_string(), call);
}
}
}
}
}
}
fn extract_atomic_var_from_args<'a>(
&self,
args_node: &Node<'a>,
source: &'a str,
atomic_vars: &HashMap<String, bool>,
) -> Option<&'a str> {
for i in 0..args_node.child_count() {
if let Some(arg) = args_node.child(i) {
if arg.kind() == "pointer_expression" {
if let Some(operand) = arg.child_by_field_name("argument") {
let var_name = get_node_text(&operand, source);
if atomic_vars.contains_key(var_name) {
return Some(var_name);
}
}
}
if arg.kind() == "identifier" {
let var_name = get_node_text(&arg, source);
if atomic_vars.contains_key(var_name) {
return Some(var_name);
}
}
}
}
None
}
}
impl CertRule for Con40C {
fn rule_id(&self) -> &'static str {
"CON40-C"
}
fn description(&self) -> &'static str {
"Do not refer to an atomic variable twice in an expression"
}
fn severity(&self) -> Severity {
Severity::Medium
}
fn category(&self) -> RuleCategory {
RuleCategory::Rule
}
fn cert_id(&self) -> &'static str {
"CON40-C"
}
fn scan(&self, node: &Node, source: &str, violations: &mut Vec<RuleViolation>) {
self.check_node(node, source, violations);
}
}