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 Flp00C;
impl CertRule for Flp00C {
fn rule_id(&self) -> &'static str {
"FLP00-C"
}
fn description(&self) -> &'static str {
"Understand the limitations of floating-point numbers"
}
fn severity(&self) -> Severity {
Severity::Medium
}
fn category(&self) -> RuleCategory {
RuleCategory::Recommendation
}
fn cert_id(&self) -> &'static str {
"FLP00-C"
}
fn check(&self, node: &Node, source: &str) -> Vec<RuleViolation> {
let mut violations = Vec::new();
self.find_fp_equality_comparisons(node, source, &mut violations);
violations
}
}
impl Flp00C {
fn find_fp_equality_comparisons(
&self,
node: &Node,
source: &str,
violations: &mut Vec<RuleViolation>,
) {
if node.kind() == "binary_expression" {
if let Some(operator) = node.child_by_field_name("operator") {
let op_text = get_node_text(&operator, source);
if op_text == "==" || op_text == "!=" {
let left = node.child_by_field_name("left");
let right = node.child_by_field_name("right");
if let (Some(left_node), Some(right_node)) = (left, right) {
let left_is_fp = self.looks_like_floating_point(&left_node, source);
let right_is_fp = self.looks_like_floating_point(&right_node, source);
let left_is_zero = self.is_zero_literal(&left_node, source);
let right_is_zero = self.is_zero_literal(&right_node, source);
if (left_is_fp || right_is_fp) && !left_is_zero && !right_is_zero {
violations.push(RuleViolation {
rule_id: self.rule_id().to_string(),
message: format!(
"Direct {} comparison of floating-point values. \
Floating-point arithmetic has finite precision and may produce \
unexpected results across platforms and optimization levels.",
if op_text == "==" {
"equality"
} else {
"inequality"
}
),
severity: self.severity(),
line: operator.start_position().row + 1,
column: operator.start_position().column + 1,
file_path: String::new(),
suggestion: Some(
"Use epsilon-based comparison (e.g., fabsf(a - b) <= __FLT_EPSILON__) \
or a relative difference function for robust floating-point comparisons"
.to_string(),
),
requires_manual_review: Some(true),
});
}
}
}
}
}
for i in 0..node.child_count() {
if let Some(child) = node.child(i) {
self.find_fp_equality_comparisons(&child, source, violations);
}
}
}
fn looks_like_floating_point(&self, node: &Node, source: &str) -> bool {
if node.kind() == "number_literal" {
let text = get_node_text(node, source);
if text.starts_with("0x") || text.starts_with("0X") {
return false;
}
if text.contains('.')
|| text.ends_with('f')
|| text.ends_with('F')
|| text.to_ascii_lowercase().contains('e')
{
return true;
}
}
if matches!(
node.kind(),
"cast_expression" | "type_descriptor" | "primitive_type"
) {
let text = get_node_text(node, source);
if text.contains("float") || text.contains("double") {
return true;
}
}
const FP_FUNCTIONS: &[&str] = &[
"sqrtf", "powf", "expf", "logf", "sinf", "cosf", "tanf", "fabsf", "fmaxf", "fminf",
"sqrt", "pow", "exp", "log", "sin", "cos", "tan", "fabs", "fmax", "fmin",
];
if node.kind() == "call_expression" {
if let Some(function) = node.child_by_field_name("function") {
let func_name = get_node_text(&function, source);
if FP_FUNCTIONS.contains(&func_name) {
return true;
}
}
}
if matches!(
node.kind(),
"binary_expression" | "unary_expression" | "parenthesized_expression"
) {
for i in 0..node.child_count() {
if let Some(child) = node.child(i) {
if self.looks_like_floating_point(&child, source) {
return true;
}
}
}
}
false
}
fn is_zero_literal(&self, node: &Node, source: &str) -> bool {
let text = get_node_text(node, source).trim();
matches!(
text,
"0.0"
| "0.0f"
| "0.0F"
| "0.00"
| "0."
| ".0"
| "0.0L"
| "0.0l"
| "0e0"
| "0.0e0"
| "0.0e+0"
| "0.0e-0"
)
}
}