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 Err00C;
impl CertRule for Err00C {
fn rule_id(&self) -> &'static str {
"ERR00-C"
}
fn description(&self) -> &'static str {
"Adopt and implement a consistent and comprehensive error-handling policy"
}
fn severity(&self) -> Severity {
Severity::Medium
}
fn category(&self) -> RuleCategory {
RuleCategory::Recommendation
}
fn cert_id(&self) -> &'static str {
"ERR00-C"
}
fn check(&self, node: &Node, source: &str) -> Vec<RuleViolation> {
let mut violations = Vec::new();
self.check_node(node, source, &mut violations);
violations
}
}
impl Err00C {
fn check_node(&self, node: &Node, source: &str, violations: &mut Vec<RuleViolation>) {
match node.kind() {
"init_declarator" => {
if let Some(declarator) = node.child_by_field_name("declarator") {
if let Some(value) = node.child_by_field_name("value") {
if value.kind() == "call_expression" {
self.check_unchecked_assignment(
&declarator,
&value,
source,
violations,
);
}
}
}
}
"assignment_expression" => {
if let Some(left) = node.child_by_field_name("left") {
if let Some(right) = node.child_by_field_name("right") {
if right.kind() == "call_expression" {
self.check_unchecked_assignment(&left, &right, source, violations);
}
}
}
}
"expression_statement" => {
if let Some(child) = node.child(0) {
if child.kind() == "call_expression" {
self.check_ignored_return(&child, source, violations);
}
}
}
_ => {}
}
for i in 0..node.child_count() {
if let Some(child) = node.child(i) {
self.check_node(&child, source, violations);
}
}
}
fn check_unchecked_assignment(
&self,
_var_node: &Node,
call_node: &Node,
source: &str,
violations: &mut Vec<RuleViolation>,
) {
if let Some(func) = call_node.child_by_field_name("function") {
let func_name = get_node_text(&func, source);
if self.is_error_prone_function(&func_name) {
if !self.has_nearby_error_check(call_node, source) {
violations.push(RuleViolation {
rule_id: self.rule_id().to_string(),
message: format!(
"Return value from {}() should be checked for errors",
func_name
),
severity: self.severity(),
line: call_node.start_position().row + 1,
column: call_node.start_position().column + 1,
file_path: String::new(),
suggestion: Some(format!(
"Check if {}() returned an error value (NULL, -1, etc.)",
func_name
)),
requires_manual_review: Some(true),
});
}
}
}
}
fn check_ignored_return(
&self,
call_node: &Node,
source: &str,
violations: &mut Vec<RuleViolation>,
) {
if let Some(func) = call_node.child_by_field_name("function") {
let func_name = get_node_text(&func, source);
if self.is_error_prone_function(&func_name) {
violations.push(RuleViolation {
rule_id: self.rule_id().to_string(),
message: format!(
"Return value from {}() is ignored. Error handling is required.",
func_name
),
severity: self.severity(),
line: call_node.start_position().row + 1,
column: call_node.start_position().column + 1,
file_path: String::new(),
suggestion: Some(format!(
"Store and check the return value from {}()",
func_name
)),
requires_manual_review: Some(false),
});
}
}
}
fn has_nearby_error_check(&self, node: &Node, source: &str) -> bool {
let mut current = node.parent();
let mut depth = 0;
while let Some(parent) = current {
depth += 1;
if depth > 3 {
break; }
if parent.kind() == "compound_statement" || parent.kind() == "translation_unit" {
for i in 0..parent.child_count() {
if let Some(sibling) = parent.child(i) {
if sibling.kind() == "if_statement" {
let text = get_node_text(&sibling, source);
if text.contains("NULL")
|| text.contains("== -1")
|| text.contains("!= 0")
|| text.contains("error")
{
return true;
}
}
}
}
}
current = parent.parent();
}
false
}
fn is_error_prone_function(&self, name: &str) -> bool {
matches!(
name,
"malloc"
| "calloc"
| "realloc"
| "fopen"
| "fgets"
| "fgetc"
| "fclose"
| "fseek"
| "ftell"
| "fread"
| "fwrite"
| "fscanf"
| "scanf"
| "sscanf"
| "strtol"
| "strtoul"
| "strdup"
)
}
}