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 Exp12C;
impl CertRule for Exp12C {
fn rule_id(&self) -> &'static str {
"EXP12-C"
}
fn description(&self) -> &'static str {
"Do not ignore values returned by functions"
}
fn severity(&self) -> Severity {
Severity::Medium
}
fn category(&self) -> RuleCategory {
RuleCategory::Recommendation
}
fn cert_id(&self) -> &'static str {
"EXP12-C"
}
fn check(&self, node: &Node, source: &str) -> Vec<RuleViolation> {
let mut violations = Vec::new();
if node.kind() == "expression_statement" {
if is_explicit_void_cast(node, source) {
} else {
check_for_ignored_return_values(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
}
}
fn is_explicit_void_cast(node: &Node, source: &str) -> bool {
for i in 0..node.child_count() {
if let Some(child) = node.child(i) {
if child.kind() == "cast_expression" {
if let Some(type_node) = child.child_by_field_name("type") {
let type_text = get_node_text(&type_node, source).trim();
if type_text == "void" {
return true;
}
}
}
}
}
false
}
fn check_for_ignored_return_values(node: &Node, source: &str, violations: &mut Vec<RuleViolation>) {
if node.kind() == "call_expression" {
if let Some(parent) = node.parent() {
let parent_kind = parent.kind();
if parent_kind == "assignment_expression"
|| parent_kind == "init_declarator"
|| parent_kind == "argument_list"
|| parent_kind == "return_statement"
|| parent_kind == "conditional_expression"
{
return;
}
}
if let Some(function_node) = node.child_by_field_name("function") {
let function_name = get_node_text(&function_node, source);
if is_function_with_important_return_value(function_name.trim()) {
report_violation(node, function_name.trim(), source, violations);
}
}
}
for i in 0..node.child_count() {
if let Some(child) = node.child(i) {
check_for_ignored_return_values(&child, source, violations);
}
}
}
fn is_function_with_important_return_value(function_name: &str) -> bool {
matches!(
function_name,
"malloc" | "calloc" | "realloc" | "aligned_alloc" |
"asprintf" | "vasprintf" |
"fopen" | "freopen" | "tmpfile" |
"fclose" | "fread" | "fwrite" | "fseek" | "ftell" |
"fflush" | "setvbuf" | "remove" | "rename" |
"scanf" | "sscanf" | "fscanf" |
"printf" | "fprintf" | "sprintf" | "snprintf" |
"system" | "atexit" | "signal" |
"open" | "close" | "read" | "write" | "lseek" |
"dup" | "dup2" | "pipe" |
"fork" | "wait" | "waitpid" | "access" |
"chdir" | "chmod" | "chown" |
"execl" | "execle" | "execlp" | "execv" | "execve" | "execvp" |
"pthread_create" | "pthread_join" | "pthread_mutex_init" |
"pthread_mutex_lock" | "pthread_mutex_unlock" |
"socket" | "bind" | "listen" | "accept" | "connect" |
"send" | "recv" | "sendto" | "recvfrom" |
"setlocale" | "getenv" | "mktime"
)
}
fn report_violation(
node: &Node,
function_name: &str,
source: &str,
violations: &mut Vec<RuleViolation>,
) {
let start_point = node.start_position();
let node_text = get_node_text(node, source);
violations.push(RuleViolation {
rule_id: "EXP12-C".to_string(),
severity: Severity::Medium,
message: format!(
"Return value of function '{}' should not be ignored: '{}'",
function_name, node_text
),
file_path: String::new(),
line: start_point.row + 1,
column: start_point.column + 1,
suggestion: Some(format!(
"Check the return value or explicitly cast to (void) if intentionally ignoring: '(void) {};'",
node_text
)),
..Default::default()
});
}