use super::super::{CertRule, RuleViolation};
use crate::manifest::{RuleCategory, Severity};
use crate::utility::cert_c::ast_utils::get_node_text;
use std::collections::HashMap;
use tree_sitter::Node;
pub struct Flp05C;
const FLOAT_MIN_NORMALIZED: f64 = 1.175e-38;
impl CertRule for Flp05C {
fn rule_id(&self) -> &'static str {
"FLP05-C"
}
fn description(&self) -> &'static str {
"Do not use denormalized numbers"
}
fn severity(&self) -> Severity {
Severity::Low
}
fn category(&self) -> RuleCategory {
RuleCategory::Recommendation
}
fn cert_id(&self) -> &'static str {
"FLP05-C"
}
fn check(&self, node: &Node, source: &str) -> Vec<RuleViolation> {
let mut violations = Vec::new();
let mut var_types: HashMap<String, String> = HashMap::new();
self.check_node(node, source, &mut violations, &mut var_types);
violations
}
}
impl Flp05C {
fn check_node(
&self,
node: &Node,
source: &str,
violations: &mut Vec<RuleViolation>,
var_types: &mut HashMap<String, String>,
) {
if node.kind() == "declaration" {
self.process_declaration(node, source, var_types);
}
if node.kind() == "binary_expression" {
self.check_binary_expression(node, source, violations, var_types);
}
if node.kind() == "init_declarator" {
self.check_init_declarator(node, source, violations);
}
for i in 0..node.child_count() {
if let Some(child) = node.child(i) {
self.check_node(&child, source, violations, var_types);
}
}
}
fn process_declaration(
&self,
node: &Node,
source: &str,
var_types: &mut HashMap<String, String>,
) {
if let Some(type_node) = node.child_by_field_name("type") {
let type_text = get_node_text(&type_node, source).to_string();
for i in 0..node.child_count() {
if let Some(child) = node.child(i) {
if child.kind() == "init_declarator" || child.kind() == "identifier" {
let var_name = if child.kind() == "init_declarator" {
child
.child_by_field_name("declarator")
.map(|d| get_node_text(&d, source).to_string())
.unwrap_or_default()
} else {
get_node_text(&child, source).to_string()
};
if !var_name.is_empty() {
var_types.insert(var_name, type_text.clone());
}
}
}
}
}
}
fn check_binary_expression(
&self,
node: &Node,
source: &str,
violations: &mut Vec<RuleViolation>,
var_types: &HashMap<String, String>,
) {
let operator = node.child_by_field_name("operator").or_else(|| {
for i in 0..node.child_count() {
if let Some(child) = node.child(i) {
let kind = child.kind();
if kind == "*" || kind == "/" {
return Some(child);
}
}
}
None
});
if let Some(op_node) = operator {
let op = get_node_text(&op_node, source);
if op == "*" || op == "/" {
if let (Some(left), Some(right)) = (
node.child_by_field_name("left"),
node.child_by_field_name("right"),
) {
let left_is_float = self.is_float_variable(&left, source, var_types);
let right_is_float = self.is_float_variable(&right, source, var_types);
let left_denorm = self.get_denormalized_value(&left, source);
let right_denorm = self.get_denormalized_value(&right, source);
if left_is_float && right_denorm.is_some() {
let pos = right.start_position();
violations.push(RuleViolation {
rule_id: self.rule_id().to_string(),
severity: Severity::Low,
message: format!(
"Float variable operated with denormalized constant {}; this can cause precision loss",
get_node_text(&right, source)
),
file_path: String::new(),
line: pos.row + 1,
column: pos.column + 1,
suggestion: Some(
"Consider using double instead of float for operations involving very small numbers".to_string(),
),
..Default::default()
});
} else if right_is_float && left_denorm.is_some() {
let pos = left.start_position();
violations.push(RuleViolation {
rule_id: self.rule_id().to_string(),
severity: Severity::Low,
message: format!(
"Float variable operated with denormalized constant {}; this can cause precision loss",
get_node_text(&left, source)
),
file_path: String::new(),
line: pos.row + 1,
column: pos.column + 1,
suggestion: Some(
"Consider using double instead of float for operations involving very small numbers".to_string(),
),
..Default::default()
});
}
}
}
}
}
fn check_init_declarator(
&self,
node: &Node,
source: &str,
violations: &mut Vec<RuleViolation>,
) {
if let Some(parent) = node.parent() {
if parent.kind() == "declaration" {
if let Some(type_node) = parent.child_by_field_name("type") {
let type_text = get_node_text(&type_node, source);
if type_text == "float" {
if let Some(value_node) = node.child_by_field_name("value") {
if self.get_denormalized_value(&value_node, source).is_some() {
let pos = value_node.start_position();
violations.push(RuleViolation {
rule_id: self.rule_id().to_string(),
severity: Severity::Low,
message: format!(
"Float initialized with denormalized value {}; this can cause precision loss",
get_node_text(&value_node, source)
),
file_path: String::new(),
line: pos.row + 1,
column: pos.column + 1,
suggestion: Some(
"Consider using double instead of float for very small values".to_string(),
),
..Default::default()
});
}
}
}
}
}
}
}
fn is_float_variable(
&self,
node: &Node,
source: &str,
var_types: &HashMap<String, String>,
) -> bool {
if node.kind() == "identifier" {
let var_name = get_node_text(node, source);
if let Some(var_type) = var_types.get(var_name) {
return var_type == "float";
}
}
false
}
fn get_denormalized_value(&self, node: &Node, source: &str) -> Option<f64> {
let text = get_node_text(node, source);
if node.kind() == "number_literal" {
if let Ok(val) = text.parse::<f64>() {
if val.abs() > 0.0 && val.abs() < FLOAT_MIN_NORMALIZED {
return Some(val);
}
}
}
None
}
}