use super::super::{CertRule, RuleViolation};
use crate::manifest::{RuleCategory, Severity};
use crate::utility::cert_c::ast_utils;
use tree_sitter::Node;
pub struct Exp32C;
impl CertRule for Exp32C {
fn rule_id(&self) -> &'static str {
"EXP32-C"
}
fn description(&self) -> &'static str {
"Do not access a volatile object through a nonvolatile reference"
}
fn severity(&self) -> Severity {
Severity::Medium
}
fn category(&self) -> RuleCategory {
RuleCategory::Rule
}
fn cert_id(&self) -> &'static str {
"EXP32-C"
}
fn check(&self, node: &Node, source: &str) -> Vec<RuleViolation> {
let mut violations = Vec::new();
if node.kind() == "assignment_expression" {
self.check_volatile_assignment(node, source, &mut violations);
}
for i in 0..node.child_count() {
if let Some(child) = node.child(i) {
violations.extend(self.check(&child, source));
}
}
violations
}
}
impl Exp32C {
fn check_volatile_assignment(
&self,
node: &Node,
source: &str,
violations: &mut Vec<RuleViolation>,
) {
if let (Some(left), Some(right)) = (
node.child_by_field_name("left"),
node.child_by_field_name("right"),
) {
let left_text = ast_utils::get_node_text(&left, source);
let right_text = ast_utils::get_node_text(&right, source);
if right_text.starts_with('&')
|| right_text.starts_with("(int**)")
|| right_text.starts_with("(int **)")
{
let left_var = left_text.trim();
if let Some(func) = ast_utils::find_containing_function(&node) {
if let Some(body) = func.child_by_field_name("body") {
let left_is_volatile_ptr_ptr =
self.is_volatile_pointer_to_pointer(left_var, &body, source);
let right_var = right_text
.trim_start_matches('&')
.trim_start_matches('(')
.trim();
let right_var = if let Some(idx) = right_var.find(')') {
right_var[..idx].trim()
} else {
right_var
};
let right_is_nonvolatile_ptr =
self.is_non_volatile_pointer(right_var, &body, source);
if left_is_volatile_ptr_ptr && right_is_nonvolatile_ptr {
violations.push(RuleViolation {
rule_id: self.rule_id().to_string(),
severity: self.severity(),
message: format!(
"Assignment of non-volatile pointer '{}' to volatile pointer-to-pointer '{}'. This may allow accessing volatile object through non-volatile reference.",
right_var, left_var
),
file_path: String::new(),
line: node.start_position().row + 1,
column: node.start_position().column + 1,
suggestion: Some(format!("Declare '{}' as volatile", right_var)),
..Default::default()
});
}
}
}
}
}
}
fn is_volatile_pointer_to_pointer(&self, var_name: &str, scope: &Node, source: &str) -> bool {
self.find_declaration_type(var_name, scope, source)
.map(|decl| decl.contains("volatile") && decl.matches("*").count() >= 2)
.unwrap_or(false)
}
fn is_non_volatile_pointer(&self, var_name: &str, scope: &Node, source: &str) -> bool {
self.find_declaration_type(var_name, scope, source)
.map(|decl| !decl.contains("volatile") && decl.contains('*'))
.unwrap_or(false)
}
fn find_declaration_type(&self, var_name: &str, scope: &Node, source: &str) -> Option<String> {
for i in 0..scope.named_child_count() {
if let Some(child) = scope.named_child(i) {
if child.kind() == "declaration" {
let decl_text = ast_utils::get_node_text(&child, source);
let pattern_semi = format!("{};", var_name);
let pattern_space = format!("{} ", var_name);
let pattern_eq = format!("{} =", var_name);
if decl_text.contains(&pattern_semi)
|| decl_text.contains(&pattern_space)
|| decl_text.contains(&pattern_eq)
{
return Some(decl_text.to_string());
}
}
if child.kind() == "compound_statement" {
if let Some(result) = self.find_declaration_type(var_name, &child, source) {
return Some(result);
}
}
}
}
None
}
}
#[cfg(test)]
mod tests {
use super::*;
fn parse_c_code(source: &str) -> tree_sitter::Tree {
let mut parser = tree_sitter::Parser::new();
parser
.set_language(&tree_sitter_c::language())
.expect("Error loading C grammar");
parser.parse(source, None).expect("Error parsing C code")
}
#[test]
fn test_volatile_mismatch() {
let code = r#"
void func(void) {
static volatile int **ipp;
static int *ip;
ipp = &ip;
}
"#;
let tree = parse_c_code(code);
let rule = Exp32C;
let violations = rule.check(&tree.root_node(), code);
assert!(
!violations.is_empty(),
"Should detect volatile mismatch in pointer assignment"
);
}
#[test]
fn test_volatile_match() {
let code = r#"
void func(void) {
static volatile int **ipp;
static volatile int *ip;
ipp = &ip;
}
"#;
let tree = parse_c_code(code);
let rule = Exp32C;
let violations = rule.check(&tree.root_node(), code);
assert!(
violations.is_empty(),
"Should not flag when volatile qualifiers match"
);
}
}