use std::collections::HashMap;
use tree_sitter::Node;
use super::super::{CertRule, RuleViolation};
use crate::manifest::{RuleCategory, Severity};
use crate::utility::cert_c::ast_utils::get_node_text;
pub struct Int13C;
impl CertRule for Int13C {
fn rule_id(&self) -> &'static str {
"INT13-C"
}
fn description(&self) -> &'static str {
"Use bitwise operators only on unsigned operands"
}
fn severity(&self) -> Severity {
Severity::Medium
}
fn category(&self) -> RuleCategory {
RuleCategory::Rule
}
fn cert_id(&self) -> &'static str {
"INT13-C"
}
fn check(&self, node: &Node, source: &str) -> Vec<RuleViolation> {
let mut violations = Vec::new();
let mut variables: HashMap<String, String> = HashMap::new();
self.collect_declarations(node, source, &mut variables);
self.check_bitwise_operations(node, source, &variables, &mut violations);
violations
}
}
impl Int13C {
fn collect_declarations(
&self,
node: &Node,
source: &str,
variables: &mut HashMap<String, String>,
) {
if node.kind() == "declaration" {
let decl_text = get_node_text(node, source);
if let Some((var_type, var_name)) = self.parse_declaration(&decl_text) {
variables.insert(var_name, var_type);
}
}
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
self.collect_declarations(&child, source, variables);
}
}
fn parse_declaration(&self, decl_text: &str) -> Option<(String, String)> {
let parts: Vec<&str> = decl_text.split_whitespace().collect();
if parts.len() >= 2 {
if parts.len() >= 3 && (parts[0] == "unsigned" || parts[0] == "signed") {
let var_type = format!("{} {}", parts[0], parts[1]);
let var_name = parts[2]
.trim_end_matches(';')
.trim_end_matches(',')
.split('=')
.next()?
.trim()
.to_string();
return Some((var_type, var_name));
} else {
let var_type = parts[0].to_string();
let var_name = parts[1]
.trim_end_matches(';')
.trim_end_matches(',')
.split('=')
.next()?
.trim()
.to_string();
return Some((var_type, var_name));
}
}
None
}
fn check_bitwise_operations(
&self,
node: &Node,
source: &str,
variables: &HashMap<String, String>,
violations: &mut Vec<RuleViolation>,
) {
if node.kind() == "binary_expression" {
if let Some(op) = node.child_by_field_name("operator") {
let op_text = get_node_text(&op, source);
if matches!(op_text.trim(), "<<" | ">>" | "&" | "|" | "^") {
if let (Some(left), Some(right)) = (
node.child_by_field_name("left"),
node.child_by_field_name("right"),
) {
if let Some(violation) =
self.check_operand_type(&left, source, variables, op_text.trim())
{
violations.push(violation);
return; }
if let Some(violation) =
self.check_operand_type(&right, source, variables, op_text.trim())
{
violations.push(violation);
return; }
}
}
}
}
if node.kind() == "unary_expression" {
if let Some(op) = node.child_by_field_name("operator") {
let op_text = get_node_text(&op, source);
if op_text.trim() == "~" {
if let Some(operand) = node.child_by_field_name("argument") {
if let Some(violation) =
self.check_operand_type(&operand, source, variables, "~")
{
violations.push(violation);
return;
}
}
}
}
}
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
self.check_bitwise_operations(&child, source, variables, violations);
}
}
fn check_operand_type(
&self,
operand: &Node,
source: &str,
variables: &HashMap<String, String>,
operator: &str,
) -> Option<RuleViolation> {
let var_name = self.extract_variable_name(operand, source)?;
if let Some(var_type) = variables.get(&var_name) {
if self.is_signed_type(var_type) {
return Some(RuleViolation {
rule_id: "INT13-C".to_string(),
message: format!(
"Bitwise operator '{}' used on signed operand '{}' of type '{}'. Use unsigned types for bitwise operations",
operator, var_name, var_type
),
severity: Severity::Medium,
line: operand.start_position().row + 1,
column: operand.start_position().column + 1,
file_path: String::new(),
suggestion: Some(format!(
"Change '{}' to an unsigned type (e.g., 'unsigned int' instead of 'int')",
var_name
)),
requires_manual_review: None,
});
}
}
None
}
fn extract_variable_name(&self, node: &Node, source: &str) -> Option<String> {
if node.kind() == "identifier" {
let text = get_node_text(node, source);
return Some(text.trim().to_string());
}
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
if let Some(name) = self.extract_variable_name(&child, source) {
return Some(name);
}
}
None
}
fn is_signed_type(&self, type_name: &str) -> bool {
matches!(
type_name,
"int"
| "short"
| "long"
| "long long"
| "char"
| "signed int"
| "signed short"
| "signed long"
| "signed long long"
| "signed char"
)
}
}