use super::super::{CertRule, RuleViolation};
use crate::analyze::context::ProjectContext;
use crate::manifest::{RuleCategory, Severity};
use crate::utility::cert_c::ast_utils::{self, get_node_text};
use crate::utility::cert_c::declarator_utils;
use crate::utility::cert_c::std_functions;
use lang_parsing_substrate::query;
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>>,
cross_file_macro_names: RefCell<HashSet<String>>,
incomplete_declarations: RefCell<bool>,
}
impl Dcl31C {
pub fn new() -> Self {
Dcl31C {
declared_functions: RefCell::new(HashSet::new()),
cross_file_functions: RefCell::new(HashSet::new()),
cross_file_macro_names: RefCell::new(HashSet::new()),
incomplete_declarations: RefCell::new(false),
}
}
fn is_known_macro(&self, name: &str, source: &str) -> bool {
ast_utils::is_defined_macro_name(name, source)
|| self.cross_file_macro_names.borrow().contains(name)
}
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 == "macro_type_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() == "function_definition" {
for name in function_pointer_param_names(node, source) {
self.declared_functions.borrow_mut().insert(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> {
let node = if node.kind() == "init_declarator" {
node.child_by_field_name("declarator")?
} else {
*node
};
let name = ast_utils::get_identifier_from_declarator(&node, source);
if name.is_empty() {
None
} else {
Some(name)
}
}
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;
}
if *self.incomplete_declarations.borrow() {
return;
}
let func_name = get_node_text(&function, source);
if is_macro_like_name(func_name) || self.is_known_macro(func_name, source) {
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>) {
for n in query::find_descendants(*node, |_| true) {
self.track_function_declaration(&n, source);
self.check_declaration(&n, source, violations);
self.check_function_call(&n, source, violations);
self.check_function_definition(&n, 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());
}
for macro_name in context.function_macros.keys() {
funcs.insert(macro_name.clone());
}
*self.cross_file_functions.borrow_mut() = funcs;
*self.cross_file_macro_names.borrow_mut() = context.defined_macro_names.clone();
*self.incomplete_declarations.borrow_mut() = !context.unresolved_project_headers.is_empty();
}
fn scan(&self, root: &Node, source: &str, violations: &mut Vec<RuleViolation>) {
self.traverse(root, source, 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 function_pointer_param_names(func_node: &Node, source: &str) -> Vec<String> {
let mut names = Vec::new();
let Some(func_declarator) = func_node
.child_by_field_name("declarator")
.and_then(|d| unwrap_to_function_declarator(&d))
else {
return names;
};
let Some(param_list) = func_declarator.child_by_field_name("parameters") else {
return names;
};
for i in 0..param_list.child_count() {
let Some(param) = param_list.child(i) else {
continue;
};
if param.kind() != "parameter_declaration" {
continue;
}
for j in 0..param.child_count() {
let Some(child) = param.child(j) else {
continue;
};
if matches!(
child.kind(),
"pointer_declarator" | "array_declarator" | "function_declarator"
) && declarator_utils::is_function_declarator(&child)
{
if let Some(name) = ast_utils::find_identifier_in_declarator(&child, source) {
names.push(name);
}
}
}
}
names
}
fn unwrap_to_function_declarator<'a>(node: &Node<'a>) -> Option<Node<'a>> {
match node.kind() {
"function_declarator" => Some(*node),
"pointer_declarator" => node
.child_by_field_name("declarator")
.and_then(|d| unwrap_to_function_declarator(&d)),
_ => None,
}
}
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
}