use super::super::{CertRule, RuleViolation};
use crate::manifest::{RuleCategory, Severity};
use crate::utility::cert_c::ast_utils::get_node_text;
use tree_sitter::Node;
pub struct Api09C;
impl CertRule for Api09C {
fn rule_id(&self) -> &'static str {
"API09-C"
}
fn description(&self) -> &'static str {
"Compatible values should have the same type"
}
fn severity(&self) -> Severity {
Severity::Low
}
fn category(&self) -> RuleCategory {
RuleCategory::Recommendation
}
fn cert_id(&self) -> &'static str {
"API09-C"
}
fn check(&self, node: &Node, source: &str) -> Vec<RuleViolation> {
let mut violations = Vec::new();
self.check_node(node, source, &mut violations);
violations
}
}
impl Api09C {
fn check_node(&self, node: &Node, source: &str, violations: &mut Vec<RuleViolation>) {
if node.kind() == "function_definition" {
self.check_function_type_compatibility(node, source, violations);
}
for i in 0..node.child_count() {
if let Some(child) = node.child(i) {
self.check_node(&child, source, violations);
}
}
}
fn check_function_type_compatibility(
&self,
function_node: &Node,
source: &str,
violations: &mut Vec<RuleViolation>,
) {
let (func_name, return_type) = match self.get_function_info(function_node, source) {
Some((name, ret_type)) => (name, ret_type),
None => return,
};
if self.is_signed_size_type(&return_type) {
if let Some(body) = function_node.child_by_field_name("body") {
if self.accumulates_size_value(&body, source) {
violations.push(RuleViolation {
rule_id: self.rule_id().to_string(),
severity: Severity::Low,
message: format!(
"Function '{}' returns signed type '{}' but accumulates size values. Consider using 'size_t' for return type.",
func_name, return_type.trim()
),
file_path: String::new(),
line: function_node.start_position().row + 1,
column: function_node.start_position().column + 1,
suggestion: Some(format!(
"Change return type from '{}' to 'size_t' to avoid implicit conversions",
return_type.trim()
)),
..Default::default()
});
}
}
}
if let Some(body) = function_node.child_by_field_name("body") {
self.check_signed_size_accumulators(&body, source, violations);
}
}
fn get_function_info(&self, function_node: &Node, source: &str) -> Option<(String, String)> {
let mut return_type = String::new();
let mut func_name = String::new();
for i in 0..function_node.child_count() {
if let Some(child) = function_node.child(i) {
match child.kind() {
"primitive_type" | "type_identifier" | "sized_type_specifier"
if return_type.is_empty() =>
{
return_type = get_node_text(&child, source).to_string();
}
"function_declarator" => {
if let Some(name) = self.extract_function_name(&child, source) {
func_name = name;
}
}
"pointer_declarator" => {
if let Some(name) = self.find_identifier(&child, source) {
func_name = name;
}
}
_ => {}
}
}
}
if func_name.is_empty() {
None
} else {
Some((func_name, return_type))
}
}
fn extract_function_name(&self, declarator_node: &Node, source: &str) -> Option<String> {
for i in 0..declarator_node.child_count() {
if let Some(child) = declarator_node.child(i) {
if child.kind() == "identifier" {
return Some(get_node_text(&child, source).to_string());
}
}
}
None
}
fn find_identifier(&self, node: &Node, source: &str) -> Option<String> {
if node.kind() == "identifier" {
return Some(get_node_text(node, source).to_string());
}
for i in 0..node.child_count() {
if let Some(child) = node.child(i) {
if let Some(id) = self.find_identifier(&child, source) {
return Some(id);
}
}
}
None
}
fn is_signed_size_type(&self, type_name: &str) -> bool {
let normalized = type_name.trim();
matches!(normalized, "ssize_t")
}
fn accumulates_size_value(&self, body: &Node, source: &str) -> bool {
self.find_size_accumulator_pattern(body, source)
}
fn find_size_accumulator_pattern(&self, node: &Node, source: &str) -> bool {
if matches!(node.kind(), "while_statement" | "for_statement") {
if let Some(body) = self.get_loop_body(node) {
if self.contains_accumulator_assignment(&body, source) {
return true;
}
}
}
if node.kind() == "return_statement" {
if let Some(value) = node.child_by_field_name("") {
let text = get_node_text(&value, source);
if matches!(text, "pos" | "count" | "total" | "bytes" | "size") {
return true;
}
}
for i in 0..node.child_count() {
if let Some(child) = node.child(i) {
if child.kind() == "identifier" {
let text = get_node_text(&child, source);
if matches!(text, "pos" | "count" | "total" | "bytes" | "size") {
return true;
}
}
}
}
}
for i in 0..node.child_count() {
if let Some(child) = node.child(i) {
if self.find_size_accumulator_pattern(&child, source) {
return true;
}
}
}
false
}
fn get_loop_body<'a>(&self, loop_node: &'a Node) -> Option<Node<'a>> {
loop_node.child_by_field_name("body")
}
fn contains_accumulator_assignment(&self, node: &Node, source: &str) -> bool {
if node.kind() == "expression_statement" {
for i in 0..node.child_count() {
if let Some(child) = node.child(i) {
if child.kind() == "assignment_expression" {
if let Some(operator) = child.child_by_field_name("operator") {
let op_text = get_node_text(&operator, source);
if op_text == "+=" {
if let Some(left) = child.child_by_field_name("left") {
let var_name = get_node_text(&left, source);
if matches!(
var_name,
"pos" | "count" | "total" | "bytes" | "size"
) {
return true;
}
}
}
}
}
}
}
}
for i in 0..node.child_count() {
if let Some(child) = node.child(i) {
if self.contains_accumulator_assignment(&child, source) {
return true;
}
}
}
false
}
fn check_signed_size_accumulators(
&self,
body: &Node,
source: &str,
violations: &mut Vec<RuleViolation>,
) {
self.find_signed_size_declarations(body, source, violations);
}
fn find_signed_size_declarations(
&self,
node: &Node,
source: &str,
violations: &mut Vec<RuleViolation>,
) {
if node.kind() == "declaration" {
let mut type_name = String::new();
let mut declarators = Vec::new();
for i in 0..node.child_count() {
if let Some(child) = node.child(i) {
match child.kind() {
"primitive_type" | "type_identifier" | "sized_type_specifier" => {
type_name = get_node_text(&child, source).to_string();
}
"init_declarator" => {
if let Some(declarator) = child.child_by_field_name("declarator") {
if let Some(var_name) =
self.get_declarator_name(&declarator, source)
{
declarators.push((var_name, child.start_position().row + 1));
}
}
}
_ => {}
}
}
}
if self.is_signed_size_type(&type_name) {
for (var_name, line) in declarators {
if matches!(
var_name.as_str(),
"pos" | "count" | "total" | "bytes" | "size"
) {
violations.push(RuleViolation {
rule_id: self.rule_id().to_string(),
severity: Severity::Low,
message: format!(
"Variable '{}' has signed type '{}' but is used to accumulate size values. Consider using 'size_t' instead.",
var_name, type_name.trim()
),
file_path: String::new(),
line,
column: node.start_position().column + 1,
suggestion: Some(format!(
"Change variable '{}' from '{}' to 'size_t' to match the semantics of size values",
var_name, type_name.trim()
)),
..Default::default()
});
}
}
}
}
for i in 0..node.child_count() {
if let Some(child) = node.child(i) {
self.find_signed_size_declarations(&child, source, violations);
}
}
}
fn get_declarator_name(&self, declarator_node: &Node, source: &str) -> Option<String> {
if declarator_node.kind() == "identifier" {
return Some(get_node_text(declarator_node, source).to_string());
}
for i in 0..declarator_node.child_count() {
if let Some(child) = declarator_node.child(i) {
if let Some(name) = self.get_declarator_name(&child, source) {
return Some(name);
}
}
}
None
}
}