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 Fio19C;
impl CertRule for Fio19C {
fn rule_id(&self) -> &'static str {
"FIO19-C"
}
fn description(&self) -> &'static str {
"Do not use fseek() and ftell() to compute the size of a regular file"
}
fn severity(&self) -> Severity {
Severity::Medium
}
fn category(&self) -> RuleCategory {
RuleCategory::Recommendation
}
fn cert_id(&self) -> &'static str {
"FIO19-C"
}
fn check(&self, node: &Node, source: &str) -> Vec<RuleViolation> {
let mut violations = Vec::new();
self.check_node(node, source, &mut violations);
violations
}
}
impl Fio19C {
fn check_node(&self, node: &Node, source: &str, violations: &mut Vec<RuleViolation>) {
if node.kind() == "call_expression" {
if let Some(func) = node.child_by_field_name("function") {
let func_name = get_node_text(&func, source).trim();
if func_name == "fseek" {
if self.fseek_uses_seek_end(node, source) {
if let Some(file_ptr) = self.get_first_argument(node) {
let file_ptr_text = get_node_text(&file_ptr, source);
if self.has_ftell_nearby(node, file_ptr_text.trim(), source) {
let line = node.start_position().row + 1;
let column = node.start_position().column + 1;
violations.push(RuleViolation {
rule_id: self.rule_id().to_string(),
severity: self.severity(),
message:
"Using fseek()/ftell() to compute file size - use fstat() or platform-specific APIs instead"
.to_string(),
file_path: String::new(),
line,
column,
suggestion: Some(
"Use fstat() with fileno() to get file size reliably"
.to_string(),
),
requires_manual_review: None,
});
}
}
}
}
}
}
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
self.check_node(&child, source, violations);
}
}
fn fseek_uses_seek_end(&self, fseek_node: &Node, source: &str) -> bool {
if let Some(args) = fseek_node.child_by_field_name("arguments") {
let mut arg_count = 0;
let mut cursor = args.walk();
for child in args.children(&mut cursor) {
if child.kind() != "," && child.kind() != "(" && child.kind() != ")" {
arg_count += 1;
if arg_count == 3 {
let arg_text = get_node_text(&child, source).trim();
return arg_text == "SEEK_END" || arg_text == "2";
}
}
}
}
false
}
fn get_first_argument<'a>(&self, call_node: &Node<'a>) -> Option<Node<'a>> {
if let Some(args) = call_node.child_by_field_name("arguments") {
let mut cursor = args.walk();
for child in args.children(&mut cursor) {
if child.kind() != "," && child.kind() != "(" && child.kind() != ")" {
return Some(child);
}
}
}
None
}
fn has_ftell_nearby(&self, fseek_node: &Node, file_ptr: &str, source: &str) -> bool {
let mut current = fseek_node.parent();
let mut scope: Option<Node> = None;
while let Some(node) = current {
if matches!(
node.kind(),
"compound_statement" | "function_definition" | "translation_unit"
) {
scope = Some(node);
break;
}
current = node.parent();
}
let scope_node = match scope {
Some(s) => s,
None => return false,
};
self.find_ftell_on_file_ptr(&scope_node, file_ptr, source)
}
fn find_ftell_on_file_ptr(&self, node: &Node, file_ptr: &str, source: &str) -> bool {
if node.kind() == "call_expression" {
if let Some(func) = node.child_by_field_name("function") {
let func_name = get_node_text(&func, source).trim();
if func_name == "ftell" {
if let Some(first_arg) = self.get_first_argument(node) {
let arg_text = get_node_text(&first_arg, source).trim();
if arg_text == file_ptr {
return true;
}
}
}
}
}
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
if self.find_ftell_on_file_ptr(&child, file_ptr, source) {
return true;
}
}
false
}
}