use super::super::{CertRule, RuleViolation};
use crate::manifest::{RuleCategory, Severity};
use tree_sitter::Node;
pub struct Int02C;
impl CertRule for Int02C {
fn rule_id(&self) -> &'static str {
"INT02-C"
}
fn description(&self) -> &'static str {
"Understand integer conversion rules"
}
fn severity(&self) -> Severity {
Severity::Medium
}
fn category(&self) -> RuleCategory {
RuleCategory::Rule
}
fn cert_id(&self) -> &'static str {
"INT02-C"
}
fn check(&self, node: &Node, source: &str) -> Vec<RuleViolation> {
let mut violations = Vec::new();
self.check_node(node, source, &mut violations);
violations
}
}
impl Int02C {
fn check_node(&self, node: &Node, source: &str, violations: &mut Vec<RuleViolation>) {
if node.kind() == "binary_expression" {
let text = node.utf8_text(source.as_bytes()).unwrap_or("");
if text.contains("<")
|| text.contains(">")
|| text.contains("<=")
|| text.contains(">=")
|| text.contains("==")
|| text.contains("!=")
{
let has_signed_pattern = text.contains("si ") || text.contains(" si");
let has_unsigned_pattern = text.contains("ui ") || text.contains(" ui");
let has_cast = text.contains("(int)") || text.contains("(unsigned");
if has_signed_pattern && has_unsigned_pattern && !has_cast {
violations.push(RuleViolation {
rule_id: self.rule_id().to_string(),
severity: self.severity(),
line: node.start_position().row + 1,
column: node.start_position().column + 1,
file_path: String::new(),
message: "Comparison between signed and unsigned integers without cast"
.to_string(),
suggestion: Some("Cast one operand to match signedness".to_string()),
requires_manual_review: None,
});
}
}
}
if node.kind() == "declaration" {
let text = node.utf8_text(source.as_bytes()).unwrap_or("");
if (text.contains("uint8_t") || text.contains("int8_t")) && text.contains("~") {
if !text.contains("(uint8_t)") && !text.contains("(int8_t)") {
violations.push(RuleViolation {
rule_id: self.rule_id().to_string(),
severity: self.severity(),
line: node.start_position().row + 1,
column: node.start_position().column + 1,
file_path: String::new(),
message:
"Bitwise operation on small integer type subject to integer promotion"
.to_string(),
suggestion: Some("Cast result explicitly to intended type".to_string()),
requires_manual_review: None,
});
}
}
if text.contains("unsigned short")
&& text.contains("*")
&& !text.contains("(unsigned int)")
{
violations.push(RuleViolation {
rule_id: self.rule_id().to_string(),
severity: self.severity(),
line: node.start_position().row + 1,
column: node.start_position().column + 1,
file_path: String::new(),
message: "Multiplication of unsigned short undergoes integer promotion; result may overflow".to_string(),
suggestion: Some("Cast operands to unsigned int before multiplication".to_string()),
requires_manual_review: None,
});
}
}
if node.kind() == "for_statement" {
let text = node.utf8_text(source.as_bytes()).unwrap_or("");
let lines_before = source
.lines()
.take(node.start_position().row)
.collect::<Vec<_>>()
.join("\n");
if text.contains("char i")
&& !text.contains("unsigned char i")
&& lines_before.contains("unsigned char max")
&& text.contains("<")
{
violations.push(RuleViolation {
rule_id: self.rule_id().to_string(),
severity: self.severity(),
line: node.start_position().row + 1,
column: node.start_position().column + 1,
file_path: String::new(),
message: "Loop compares signed char with unsigned char; signedness mismatch"
.to_string(),
suggestion: Some(
"Use consistent signedness for loop variable and limit".to_string(),
),
requires_manual_review: None,
});
}
}
if node.kind() == "assignment_expression" || node.kind() == "init_declarator" {
let text = node.utf8_text(source.as_bytes()).unwrap_or("");
let lines_before = source
.lines()
.take(node.start_position().row)
.collect::<Vec<_>>()
.join("\n");
if text.contains("*")
&& lines_before.contains("unsigned short")
&& !text.contains("(unsigned int)")
&& !text.contains("(unsigned long)")
{
violations.push(RuleViolation {
rule_id: self.rule_id().to_string(),
severity: self.severity(),
line: node.start_position().row + 1,
column: node.start_position().column + 1,
file_path: String::new(),
message: "Multiplication of unsigned short values; result may overflow due to integer promotion".to_string(),
suggestion: Some("Cast operands to unsigned int before multiplication".to_string()),
requires_manual_review: None,
});
}
}
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
self.check_node(&child, source, violations);
}
}
}