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 Pre01C;
impl Pre01C {
pub fn new() -> Self {
Self
}
fn extract_parameters(&self, node: &Node, source: &str) -> Vec<String> {
let mut params = Vec::new();
if let Some(params_node) = node.child_by_field_name("parameters") {
for i in 0..params_node.child_count() {
if let Some(child) = params_node.child(i) {
if child.kind() == "identifier" {
let param_name = get_node_text(&child, source).trim().to_string();
params.push(param_name);
}
}
}
}
params
}
fn is_parenthesized(&self, param_start: usize, param_end: usize, text: &str) -> bool {
let before = &text[..param_start];
let after = &text[param_end..];
let has_open_paren = before.trim_end().ends_with('(');
let has_close_paren = after.trim_start().starts_with(')');
has_open_paren && has_close_paren
}
fn is_token_concatenation(&self, param_start: usize, param_end: usize, text: &str) -> bool {
let before = &text[..param_start];
let after = &text[param_end..];
before.trim_end().ends_with("##") || after.trim_start().starts_with("##")
}
fn is_stringification(&self, param_start: usize, text: &str) -> bool {
let before = &text[..param_start];
let trimmed = before.trim_end();
if trimmed.ends_with('#') {
return !trimmed.ends_with("##");
}
false
}
fn is_in_function_args(&self, param_start: usize, param_end: usize, text: &str) -> bool {
let before = &text[..param_start];
let after = &text[param_end..];
let mut depth = 0;
let mut last_was_open = false;
for c in before.chars().rev() {
if c == ')' {
depth += 1;
} else if c == '(' {
if depth == 0 {
last_was_open = true;
break;
}
depth -= 1;
}
}
let trimmed_after = after.trim_start();
let has_comma_or_close = trimmed_after.starts_with(',') || trimmed_after.starts_with(')');
last_was_open && has_comma_or_close
}
fn check_function_macro(&self, node: &Node, source: &str, violations: &mut Vec<RuleViolation>) {
if node.kind() != "preproc_function_def" {
return;
}
let params = self.extract_parameters(node, source);
if params.is_empty() {
return; }
let value_node = match node.child_by_field_name("value") {
Some(v) => v,
None => return, };
let value_text = get_node_text(&value_node, source);
for param in ¶ms {
let mut start_pos = 0;
while let Some(pos) = value_text[start_pos..].find(param.as_str()) {
let absolute_pos = start_pos + pos;
let param_end = absolute_pos + param.len();
let before_char = if absolute_pos > 0 {
value_text.chars().nth(absolute_pos - 1)
} else {
None
};
let after_char = value_text.chars().nth(param_end);
let is_complete_match = (before_char.is_none()
|| !before_char.unwrap().is_alphanumeric() && before_char.unwrap() != '_')
&& (after_char.is_none()
|| !after_char.unwrap().is_alphanumeric() && after_char.unwrap() != '_');
if is_complete_match {
let is_concat =
self.is_token_concatenation(absolute_pos, param_end, &value_text);
let is_stringify = self.is_stringification(absolute_pos, &value_text);
let is_func_arg =
self.is_in_function_args(absolute_pos, param_end, &value_text);
if !is_concat && !is_stringify && !is_func_arg {
if !self.is_parenthesized(absolute_pos, param_end, &value_text) {
violations.push(RuleViolation {
rule_id: self.rule_id().to_string(),
severity: self.severity(),
message: format!(
"Macro parameter '{}' is not parenthesized in replacement text. This can cause operator precedence issues.",
param
),
file_path: String::new(),
line: node.start_position().row + 1,
column: node.start_position().column + 1,
suggestion: Some(format!(
"Wrap parameter '{}' in parentheses: ({}) instead of {}",
param, param, param
)),
..Default::default()
});
break; }
}
}
start_pos = param_end;
}
}
}
}
impl CertRule for Pre01C {
fn rule_id(&self) -> &'static str {
"PRE01-C"
}
fn description(&self) -> &'static str {
"Use parentheses within macros around parameter names"
}
fn severity(&self) -> Severity {
Severity::Medium
}
fn category(&self) -> RuleCategory {
RuleCategory::Rule
}
fn cert_id(&self) -> &'static str {
"PRE01-C"
}
fn check(&self, node: &Node, source: &str) -> Vec<RuleViolation> {
let mut violations = Vec::new();
self.check_node(node, source, &mut violations);
violations
}
}
impl Pre01C {
fn check_node(&self, node: &Node, source: &str, violations: &mut Vec<RuleViolation>) {
self.check_function_macro(node, source, violations);
for i in 0..node.child_count() {
if let Some(child) = node.child(i) {
self.check_node(&child, source, violations);
}
}
}
}