use super::super::{CertRule, RuleViolation};
use crate::manifest::{RuleCategory, Severity};
use crate::utility::cert_c::ast_utils::get_node_text;
use std::collections::HashSet;
use tree_sitter::Node;
pub struct Fio08C;
impl CertRule for Fio08C {
fn rule_id(&self) -> &'static str {
"FIO08-C"
}
fn description(&self) -> &'static str {
"Take care when calling remove() on an open file"
}
fn severity(&self) -> Severity {
Severity::Medium
}
fn category(&self) -> RuleCategory {
RuleCategory::Recommendation
}
fn cert_id(&self) -> &'static str {
"FIO08-C"
}
fn check(&self, node: &Node, source: &str) -> Vec<RuleViolation> {
let mut violations = Vec::new();
let mut open_files: HashSet<String> = HashSet::new();
self.analyze_file_operations(node, source, &mut open_files, &mut violations);
violations
}
}
impl Fio08C {
fn analyze_file_operations(
&self,
node: &Node,
source: &str,
open_files: &mut HashSet<String>,
violations: &mut Vec<RuleViolation>,
) {
if node.kind() == "call_expression" {
if let Some(function) = node.child_by_field_name("function") {
let func_name = get_node_text(&function, source);
match func_name {
"fopen" | "fopen_s" | "freopen" => {
if let Some(args) = node.child_by_field_name("arguments") {
if let Some(filename) = self.get_first_arg(&args, source) {
open_files.insert(filename);
}
}
}
"fclose" => {
}
"remove" => {
if let Some(args) = node.child_by_field_name("arguments") {
if let Some(filename) = self.get_first_arg(&args, source) {
if open_files.contains(&filename) {
violations.push(RuleViolation {
rule_id: self.rule_id().to_string(),
message: format!(
"Calling remove() on '{}' which may still be open. \
This is implementation-defined behavior.",
filename
),
severity: self.severity(),
line: node.start_position().row + 1,
column: node.start_position().column + 1,
file_path: String::new(),
suggestion: Some(
"Close the file with fclose() before calling remove(), \
or use unlink() for POSIX systems"
.to_string(),
),
requires_manual_review: None,
});
}
}
}
}
_ => {}
}
}
}
for i in 0..node.child_count() {
if let Some(child) = node.child(i) {
self.analyze_file_operations(&child, source, open_files, violations);
}
}
}
fn get_first_arg(&self, args: &Node, source: &str) -> Option<String> {
for i in 0..args.child_count() {
if let Some(child) = args.child(i) {
if child.kind() != "(" && child.kind() != ")" && child.kind() != "," {
return Some(get_node_text(&child, source).to_string());
}
}
}
None
}
}