use super::super::{CertRule, RuleViolation};
use crate::manifest::{RuleCategory, Severity};
use crate::utility::cert_c::ast_utils::get_node_text;
use tree_sitter::Node;
#[derive(Debug)]
pub struct Fio09C;
impl Fio09C {
#[allow(dead_code)]
pub fn new() -> Self {
Fio09C
}
#[allow(dead_code)]
fn is_struct_pointer(&self, node: &Node, source: &str) -> bool {
let text = get_node_text(node, source);
if let Some(_child) = node.child_by_field_name("argument") {
if node.kind() == "pointer_expression" {
return true; }
}
text.contains("struct ") ||
node.kind() == "pointer_expression" ||
(node.kind() == "identifier" && !text.starts_with('"'))
}
fn is_binary_io_call(&self, node: &Node, source: &str) -> bool {
if node.kind() != "call_expression" {
return false;
}
if let Some(function) = node.child_by_field_name("function") {
let func_name = get_node_text(&function, source);
func_name == "fread" || func_name == "fwrite"
} else {
false
}
}
fn get_first_argument<'a>(&self, arguments: &'a Node) -> Option<Node<'a>> {
let mut cursor = arguments.walk();
Self::find_non_punctuation(arguments.children(&mut cursor))
}
fn find_non_punctuation<'a>(iter: impl Iterator<Item = Node<'a>>) -> Option<Node<'a>> {
iter.into_iter()
.find(|child| child.kind() != "(" && child.kind() != ")" && child.kind() != ",")
}
fn check_binary_io(&self, node: &Node, source: &str, violations: &mut Vec<RuleViolation>) {
if !self.is_binary_io_call(node, source) {
return;
}
let func_name = if let Some(function) = node.child_by_field_name("function") {
get_node_text(&function, source).to_string()
} else {
return;
};
if let Some(arguments) = node.child_by_field_name("arguments") {
if let Some(first_arg) = self.get_first_argument(&arguments) {
let arg_text = get_node_text(&first_arg, source);
let is_suspicious = first_arg.kind() == "pointer_expression" || arg_text.contains("struct ") || (first_arg.kind() == "identifier" &&
self.has_sizeof_struct_in_args(&arguments, source));
if is_suspicious {
violations.push(RuleViolation {
rule_id: "FIO09-C".to_string(),
severity: Severity::Medium,
line: node.start_position().row + 1,
column: node.start_position().column + 1,
message: format!(
"{}() used for binary I/O with potentially structured data; portability issues may arise from differences in structure padding, endianness, or floating-point representation",
func_name
),
file_path: String::new(),
suggestion: Some(
"Use text-based format (e.g., fgets/fprintf) or a specialized serialization library to ensure portability across systems".to_string(),
),
requires_manual_review: Some(false),
});
}
}
}
}
fn has_sizeof_struct_in_args(&self, arguments: &Node, source: &str) -> bool {
let mut cursor = arguments.walk();
for child in arguments.children(&mut cursor) {
if child.kind() == "sizeof_expression" {
let sizeof_text = get_node_text(&child, source);
if sizeof_text.contains("struct ") {
return true;
}
}
}
false
}
fn traverse(&self, node: &Node, source: &str, violations: &mut Vec<RuleViolation>) {
if node.kind() == "call_expression" {
self.check_binary_io(node, source, violations);
}
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
self.traverse(&child, source, violations);
}
}
}
impl CertRule for Fio09C {
fn rule_id(&self) -> &'static str {
"FIO09-C"
}
fn description(&self) -> &'static str {
"Be careful with binary data when transferring data across systems"
}
fn category(&self) -> RuleCategory {
RuleCategory::Recommendation
}
fn severity(&self) -> Severity {
Severity::Medium
}
fn cert_id(&self) -> &'static str {
"FIO09-C"
}
fn check(&self, root: &Node, source: &str) -> Vec<RuleViolation> {
let mut violations = Vec::new();
self.traverse(root, source, &mut violations);
violations
}
}