use crate::manifest::{RuleCategory, Severity};
use crate::rules::{CertRule, RuleViolation};
use crate::utility::cert_c::ast_utils::get_node_text;
use std::collections::HashSet;
use tree_sitter::Node;
pub struct Api05C;
impl CertRule for Api05C {
fn rule_id(&self) -> &'static str {
"API05-C"
}
fn description(&self) -> &'static str {
"Use conformant array parameters"
}
fn severity(&self) -> Severity {
Severity::High
}
fn category(&self) -> RuleCategory {
RuleCategory::Recommendation
}
fn cert_id(&self) -> &'static str {
"API05-C"
}
fn check(&self, node: &Node, source: &str) -> Vec<RuleViolation> {
let mut violations = Vec::new();
self.check_node(node, source, &mut violations);
violations
}
}
impl Api05C {
fn check_node(&self, node: &Node, source: &str, violations: &mut Vec<RuleViolation>) {
if node.kind() == "function_definition" || node.kind() == "declaration" {
if let Some(declarator) = node.child_by_field_name("declarator") {
self.check_function_declarator(&declarator, source, violations);
}
}
for i in 0..node.child_count() {
if let Some(child) = node.child(i) {
self.check_node(&child, source, violations);
}
}
}
fn check_function_declarator(
&self,
declarator: &Node,
source: &str,
violations: &mut Vec<RuleViolation>,
) {
if declarator.kind() == "function_declarator" {
if let Some(params) = declarator.child_by_field_name("parameters") {
self.check_parameters(¶ms, source, violations);
}
} else if declarator.kind() == "pointer_declarator" {
if let Some(child) = declarator.named_child(0) {
self.check_function_declarator(&child, source, violations);
}
}
}
fn check_parameters(
&self,
params_node: &Node,
source: &str,
violations: &mut Vec<RuleViolation>,
) {
let params_text = get_node_text(params_node, source);
if params_text.contains(';') {
return;
}
let mut param_names: Vec<String> = Vec::new();
let mut param_nodes: Vec<Node> = Vec::new();
let mut has_size_t_param = false;
for i in 0..params_node.child_count() {
if let Some(child) = params_node.child(i) {
if child.kind() == "parameter_declaration" {
if let Some(name) = self.get_parameter_name(&child, source) {
param_names.push(name);
param_nodes.push(child);
if let Some(type_node) = child.child_by_field_name("type") {
let type_text = get_node_text(&type_node, source);
if type_text.contains("size_t") {
has_size_t_param = true;
}
}
}
}
}
}
for (idx, param_node) in param_nodes.iter().enumerate() {
let declared_names: HashSet<_> = param_names[..idx].iter().cloned().collect();
self.check_parameter_conformance(
param_node,
source,
&declared_names,
has_size_t_param,
violations,
);
}
}
fn get_parameter_name(&self, param: &Node, source: &str) -> Option<String> {
if let Some(declarator) = param.child_by_field_name("declarator") {
return self.extract_declarator_name(&declarator, source);
}
None
}
#[allow(clippy::only_used_in_recursion)]
fn extract_declarator_name(&self, declarator: &Node, source: &str) -> Option<String> {
match declarator.kind() {
"identifier" => Some(get_node_text(declarator, source).to_string()),
"pointer_declarator" | "array_declarator" | "function_declarator" => {
for i in 0..declarator.child_count() {
if let Some(child) = declarator.child(i) {
if let Some(name) = self.extract_declarator_name(&child, source) {
return Some(name);
}
}
}
None
}
_ => None,
}
}
fn check_parameter_conformance(
&self,
param: &Node,
source: &str,
declared_names: &HashSet<String>,
has_size_t_param: bool,
violations: &mut Vec<RuleViolation>,
) {
if let Some(declarator) = param.child_by_field_name("declarator") {
if has_size_t_param && self.is_plain_pointer_param(param, &declarator, source) {
violations.push(RuleViolation {
rule_id: self.rule_id().to_string(),
severity: self.severity(),
message: "Pointer parameter should use conformant array syntax".to_string(),
file_path: String::new(),
line: declarator.start_position().row + 1,
column: declarator.start_position().column + 1,
suggestion: Some(
"Use conformant array parameter syntax (e.g., 'char p[n]') \
with the size parameter declared before the array"
.to_string(),
),
..Default::default()
});
}
self.check_declarator_conformance(&declarator, source, declared_names, violations);
}
}
fn is_plain_pointer_param(&self, param: &Node, declarator: &Node, source: &str) -> bool {
if declarator.kind() == "pointer_declarator" {
let has_array_or_func = self.has_nested_array_or_function(declarator);
if !has_array_or_func {
if let Some(type_node) = param.child_by_field_name("type") {
let type_text = get_node_text(&type_node, source);
if type_text.contains("char")
|| type_text.contains("void")
|| type_text.contains("unsigned")
|| type_text.contains("int")
{
return true;
}
}
}
}
false
}
#[allow(clippy::only_used_in_recursion)]
fn has_nested_array_or_function(&self, node: &Node) -> bool {
for i in 0..node.child_count() {
if let Some(child) = node.child(i) {
if child.kind() == "array_declarator" || child.kind() == "function_declarator" {
return true;
}
if self.has_nested_array_or_function(&child) {
return true;
}
}
}
false
}
fn check_declarator_conformance(
&self,
declarator: &Node,
source: &str,
declared_names: &HashSet<String>,
violations: &mut Vec<RuleViolation>,
) {
if declarator.kind() == "array_declarator" {
if let Some(size_node) = declarator.child_by_field_name("size") {
let size_text = get_node_text(&size_node, source).trim();
if size_node.kind() == "identifier"
|| (size_node.kind() == "subscript_expression"
&& size_node.named_child_count() > 0
&& size_node
.named_child(0)
.is_some_and(|n| n.kind() == "identifier"))
{
let var_name = if size_node.kind() == "identifier" {
size_text.to_string()
} else if let Some(first_child) = size_node.named_child(0) {
get_node_text(&first_child, source).to_string()
} else {
return;
};
if !declared_names.contains(&var_name) {
violations.push(RuleViolation {
rule_id: self.rule_id().to_string(),
severity: self.severity(),
message: format!(
"Array parameter uses size variable '{}' that is declared after the array parameter",
var_name
),
file_path: String::new(),
line: declarator.start_position().row + 1,
column: declarator.start_position().column + 1,
suggestion: Some(format!(
"Declare size parameter '{}' before the array parameter, or use K&R style with semicolon",
var_name
)),
..Default::default()
});
}
}
}
} else if declarator.kind() == "pointer_declarator" {
for i in 0..declarator.child_count() {
if let Some(child) = declarator.child(i) {
self.check_declarator_conformance(&child, source, declared_names, violations);
}
}
}
}
}