use super::super::{CertRule, RuleViolation};
use crate::analyze::context::ProjectContext;
use crate::manifest::{RuleCategory, Severity};
use crate::utility::cert_c::ast_utils::get_node_text;
use crate::utility::cert_c::std_functions;
use std::cell::RefCell;
use std::collections::HashSet;
use tree_sitter::Node;
#[derive(Debug)]
pub struct Dcl31C {
declared_functions: RefCell<HashSet<String>>,
cross_file_functions: RefCell<HashSet<String>>,
}
impl Dcl31C {
pub fn new() -> Self {
Dcl31C {
declared_functions: RefCell::new(HashSet::new()),
cross_file_functions: RefCell::new(HashSet::new()),
}
}
fn has_type_specifier(&self, node: &Node, source: &str) -> bool {
let mut cursor = node.walk();
let mut has_storage_class = false;
let mut has_explicit_type = false;
let mut has_real_declarator = false;
let mut type_identifier_name: Option<String> = None;
for child in node.children(&mut cursor) {
let kind = child.kind();
if matches!(
kind,
"primitive_type"
| "sized_type_specifier"
| "struct_specifier"
| "union_specifier"
| "enum_specifier"
) {
has_explicit_type = true;
}
if kind == "type_identifier" {
type_identifier_name = Some(get_node_text(&child, source).to_string());
}
if kind == "storage_class_specifier" {
has_storage_class = true;
}
if kind == "identifier" {
let text = get_node_text(&child, source);
if !text.is_empty() {
has_real_declarator = true;
}
} else if kind.contains("declarator") {
has_real_declarator = true;
}
}
if has_storage_class && type_identifier_name.is_some() && !has_real_declarator {
return false;
}
if has_storage_class && !has_explicit_type && type_identifier_name.is_none() {
return false;
}
has_explicit_type || type_identifier_name.is_some()
}
fn check_declaration(&self, node: &Node, source: &str, violations: &mut Vec<RuleViolation>) {
if node.kind() == "declaration" {
if !self.has_type_specifier(node, source) {
violations.push(RuleViolation {
rule_id: "DCL31-C".to_string(),
severity: Severity::Low,
line: node.start_position().row + 1,
column: node.start_position().column + 1,
message: "Declaration is missing an explicit type specifier".to_string(),
file_path: String::new(),
suggestion: Some(
"Add an explicit type specifier to the declaration".to_string(),
),
requires_manual_review: Some(false),
});
}
}
}
fn track_function_declaration(&self, node: &Node, source: &str) {
if node.kind() == "function_definition" || node.kind() == "declaration" {
if let Some(declarator) = node.child_by_field_name("declarator") {
if let Some(func_name) = self.extract_function_name(&declarator, source) {
self.declared_functions.borrow_mut().insert(func_name);
}
}
}
if node.kind() == "preproc_function_def" {
if let Some(name_node) = node.child_by_field_name("name") {
let name = get_node_text(&name_node, source).to_string();
self.declared_functions.borrow_mut().insert(name);
}
}
}
fn extract_function_name(&self, node: &Node, source: &str) -> Option<String> {
match node.kind() {
"function_declarator" => {
if let Some(declarator) = node.child_by_field_name("declarator") {
return self.extract_function_name(&declarator, source);
}
}
"pointer_declarator" => {
if let Some(declarator) = node.child_by_field_name("declarator") {
return self.extract_function_name(&declarator, source);
}
}
"identifier" => {
return Some(get_node_text(node, source).to_string());
}
_ => {}
}
None
}
fn check_function_call(&self, node: &Node, source: &str, violations: &mut Vec<RuleViolation>) {
if node.kind() == "call_expression" {
if let Some(function) = node.child_by_field_name("function") {
if function.kind() != "identifier" {
return;
}
let func_name = get_node_text(&function, source);
if is_macro_like_name(func_name) {
return;
}
if func_name == "defined" {
return;
}
if func_name.starts_with('_') {
return;
}
if std_functions::is_known_standard_function(func_name) {
return;
}
if self.declared_functions.borrow().contains(func_name) {
return;
}
if self.cross_file_functions.borrow().contains(func_name) {
return;
}
if is_inside_preproc_conditional(node) {
return;
}
violations.push(RuleViolation {
rule_id: "DCL31-C".to_string(),
severity: Severity::Low,
line: node.start_position().row + 1,
column: node.start_position().column + 1,
message: format!(
"Function '{}' is called without prior declaration",
func_name
),
file_path: String::new(),
suggestion: Some(
"Declare the function before calling it or include the appropriate header"
.to_string(),
),
requires_manual_review: Some(false),
});
}
}
}
fn check_function_definition(
&self,
node: &Node,
source: &str,
violations: &mut Vec<RuleViolation>,
) {
if node.kind() == "function_definition" {
if !self.has_type_specifier(node, source) {
violations.push(RuleViolation {
rule_id: "DCL31-C".to_string(),
severity: Severity::Low,
line: node.start_position().row + 1,
column: node.start_position().column + 1,
message: "Function definition is missing an explicit return type".to_string(),
file_path: String::new(),
suggestion: Some(
"Add an explicit return type to the function definition".to_string(),
),
requires_manual_review: Some(false),
});
}
}
}
fn traverse(&self, node: &Node, source: &str, violations: &mut Vec<RuleViolation>) {
self.track_function_declaration(node, source);
self.check_declaration(node, source, violations);
self.check_function_call(node, source, violations);
self.check_function_definition(node, source, violations);
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
self.traverse(&child, source, violations);
}
}
}
impl CertRule for Dcl31C {
fn rule_id(&self) -> &'static str {
"DCL31-C"
}
fn description(&self) -> &'static str {
"Declare identifiers before using them"
}
fn category(&self) -> RuleCategory {
RuleCategory::Rule
}
fn severity(&self) -> Severity {
Severity::Low
}
fn cert_id(&self) -> &'static str {
"DCL31-C"
}
fn set_project_context(&self, context: &ProjectContext) {
let mut funcs = context.known_functions.clone();
funcs.extend(context.header_declared_functions.clone());
for alias_name in context.macro_aliases.keys() {
funcs.insert(alias_name.clone());
}
*self.cross_file_functions.borrow_mut() = funcs;
}
fn check(&self, root: &Node, source: &str) -> Vec<RuleViolation> {
let mut violations = Vec::new();
self.traverse(root, source, &mut violations);
violations
}
}
fn is_macro_like_name(name: &str) -> bool {
!name.is_empty()
&& name
.chars()
.all(|c| c.is_ascii_uppercase() || c.is_ascii_digit() || c == '_')
}
fn is_inside_preproc_conditional(node: &Node) -> bool {
let mut current = *node;
while let Some(parent) = current.parent() {
match parent.kind() {
"preproc_ifdef" | "preproc_if" | "preproc_elif" => return true,
_ => {}
}
current = parent;
}
false
}