use crate::manifest::{RuleCategory, Severity};
use crate::rules::{CertRule, RuleViolation};
use crate::utility::cert_c::ast_utils::get_node_text;
use lang_parsing_substrate::query;
use std::collections::HashSet;
use tree_sitter::Node;
const BUFFER_OP_CALLS: &[&str] = &[
"memcpy",
"memmove",
"memset",
"memcmp",
"strncpy",
"strncat",
"strncmp",
"strlcpy",
"strlcat",
"snprintf",
"vsnprintf",
"read",
"write",
"recv",
"send",
"fread",
"fwrite",
];
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();
for decl in query::find_descendants_of_kinds(*node, &["function_definition", "declaration"])
{
let body = decl.child_by_field_name("body");
if let Some(declarator) = decl.child_by_field_name("declarator") {
self.check_function_declarator(&declarator, source, body.as_ref(), &mut violations);
}
}
violations
}
}
impl Api05C {
fn check_function_declarator(
&self,
declarator: &Node,
source: &str,
body: Option<&Node>,
violations: &mut Vec<RuleViolation>,
) {
if declarator.kind() == "function_declarator" {
if let Some(params) = declarator.child_by_field_name("parameters") {
self.check_parameters(¶ms, source, body, violations);
}
} else if declarator.kind() == "pointer_declarator" {
if let Some(child) = declarator.named_child(0) {
self.check_function_declarator(&child, source, body, violations);
}
}
}
fn check_parameters(
&self,
params_node: &Node,
source: &str,
body: Option<&Node>,
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 size_t_param_names: Vec<String> = Vec::new();
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.clone());
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") {
size_t_param_names.push(name);
}
}
}
}
}
}
let all_param_names: HashSet<&String> = param_names.iter().collect();
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,
¶m_names[idx],
source,
&declared_names,
&all_param_names,
&size_t_param_names,
body,
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,
}
}
#[allow(clippy::too_many_arguments)]
fn check_parameter_conformance(
&self,
param: &Node,
param_name: &str,
source: &str,
declared_names: &HashSet<String>,
all_param_names: &HashSet<&String>,
size_t_param_names: &[String],
body: Option<&Node>,
violations: &mut Vec<RuleViolation>,
) {
if let Some(declarator) = param.child_by_field_name("declarator") {
if let Some(body) = body {
if self.is_plain_pointer_param(param, &declarator, source) {
if let Some(size_name) = size_t_param_names.iter().find(|n| {
Self::body_associates_pointer_with_size(body, source, param_name, n)
}) {
violations.push(RuleViolation {
rule_id: self.rule_id().to_string(),
severity: self.severity(),
message: format!(
"Pointer parameter '{}' should use conformant array syntax bounded by '{}'",
param_name, size_name
),
file_path: String::new(),
line: declarator.start_position().row + 1,
column: declarator.start_position().column + 1,
suggestion: Some(format!(
"Use conformant array parameter syntax (e.g., '{}[{}]') \
with the size parameter declared before the array",
param_name, size_name
)),
..Default::default()
});
}
}
}
self.check_declarator_conformance(
&declarator,
source,
declared_names,
all_param_names,
violations,
);
}
}
fn is_plain_pointer_param(&self, param: &Node, declarator: &Node, source: &str) -> bool {
if declarator.kind() != "pointer_declarator" {
return false;
}
if self.has_nested_array_or_function(declarator) {
return false;
}
if declarator
.named_child(0)
.is_some_and(|c| c.kind() == "pointer_declarator")
{
return false;
}
let Some(type_node) = param.child_by_field_name("type") else {
return false;
};
let type_text = get_node_text(&type_node, source);
if type_text.trim() == "void" {
return false;
}
let full_param_text = get_node_text(param, source);
if full_param_text.contains("const")
&& type_text.contains("char")
&& !type_text.contains("unsigned")
&& !type_text.contains("signed")
{
return false;
}
type_text.contains("char")
|| type_text.contains("void")
|| type_text.contains("unsigned")
|| type_text.contains("int")
}
fn has_nested_array_or_function(&self, node: &Node) -> bool {
query::find_first_descendant(*node, |n| {
n.kind() == "array_declarator" || n.kind() == "function_declarator"
})
.is_some()
}
fn body_associates_pointer_with_size(
body: &Node,
source: &str,
ptr_name: &str,
size_name: &str,
) -> bool {
for sub in query::find_descendants_of_kind(*body, "subscript_expression") {
let Some(arr) = sub.child_by_field_name("argument") else {
continue;
};
if arr.kind() != "identifier" || get_node_text(&arr, source) != ptr_name {
continue;
}
let Some(idx) = sub.child_by_field_name("index") else {
continue;
};
if idx.kind() != "binary_expression" {
continue;
}
let idx_text = get_node_text(&idx, source);
if Self::dominant_identifier(&idx, source) == size_name
&& idx_text.contains('-')
&& !idx_text.contains('+')
{
return true;
}
}
for call in query::find_descendants_of_kind(*body, "call_expression") {
let Some(func) = call.child_by_field_name("function") else {
continue;
};
let func_name = get_node_text(&func, source);
if !BUFFER_OP_CALLS.contains(&func_name) {
continue;
}
let Some(args) = call.child_by_field_name("arguments") else {
continue;
};
let mut has_ptr = false;
let mut has_size = false;
let mut cursor = args.walk();
for arg in args.named_children(&mut cursor) {
let text = get_node_text(&arg, source).trim();
if text == ptr_name {
has_ptr = true;
}
if text == size_name {
has_size = true;
}
}
if has_ptr && has_size {
return true;
}
}
false
}
fn dominant_identifier(node: &Node, source: &str) -> String {
if node.kind() == "identifier" {
return get_node_text(node, source).to_string();
}
for i in 0..node.child_count() {
if let Some(child) = node.child(i) {
if child.kind() == "identifier" {
return get_node_text(&child, source).to_string();
}
}
}
String::new()
}
fn check_declarator_conformance(
&self,
declarator: &Node,
source: &str,
declared_names: &HashSet<String>,
all_param_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 !all_param_names.contains(&var_name) {
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,
all_param_names,
violations,
);
}
}
}
}
}