use super::super::{CertRule, RuleViolation};
use crate::manifest::{RuleCategory, Severity};
use crate::utility::cert_c::ast_utils::get_node_text;
use tree_sitter::Node;
const RESERVED_NAMES: &[&str] = &[
"errno",
"SIZE_MAX",
"INT_MAX",
"INT_MIN",
"UINT_MAX",
"LONG_MAX",
"LONG_MIN",
"ULONG_MAX",
"CHAR_MAX",
"CHAR_MIN",
"SHRT_MAX",
"SHRT_MIN",
"USHRT_MAX",
"LLONG_MAX",
"LLONG_MIN",
"ULLONG_MAX",
"NULL",
"true",
"false",
"bool",
"stdin",
"stdout",
"stderr",
"FILE",
"EOF",
];
const RESERVED_FUNCTIONS: &[&str] = &[
"malloc", "calloc", "realloc", "free", "printf", "scanf", "fprintf", "sprintf", "fopen",
"fclose", "fread", "fwrite", "memcpy", "memset", "strlen", "strcpy", "strcat", "strcmp",
"exit", "abort",
];
#[derive(Debug)]
pub struct Dcl37C;
impl Dcl37C {
pub fn new() -> Self {
Dcl37C
}
fn is_reserved_identifier(&self, name: &str) -> bool {
if name.starts_with("__") {
return true;
}
if name.starts_with('_') && name.len() > 1 {
if let Some(second_char) = name.chars().nth(1) {
if second_char.is_uppercase() {
return true;
}
}
}
RESERVED_NAMES.contains(&name)
}
fn is_reserved_function(&self, name: &str) -> bool {
RESERVED_FUNCTIONS.contains(&name)
}
fn has_file_scope_reserved_prefix(&self, name: &str) -> bool {
name.starts_with('_')
}
fn check_node(&self, node: &Node, source: &str, violations: &mut Vec<RuleViolation>) {
match node.kind() {
"declaration" => {
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
if child.kind() == "init_declarator" {
if let Some(declarator) = child.child_by_field_name("declarator") {
self.check_file_scope_declarator(&declarator, source, violations);
}
}
}
if let Some(declarator) = node.child_by_field_name("declarator") {
self.check_file_scope_declarator(&declarator, source, violations);
}
}
"preproc_def" | "preproc_function_def" => {
self.check_preproc_def(node, source, violations);
}
"function_definition" => {
if let Some(declarator) = node.child_by_field_name("declarator") {
self.check_function_declarator(&declarator, source, violations);
}
}
"parameter_declaration" => {
if let Some(declarator) = node.child_by_field_name("declarator") {
self.check_declarator(&declarator, source, violations);
}
}
_ => {}
}
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
self.check_node(&child, source, violations);
}
}
#[allow(dead_code)]
fn check_declaration(&self, node: &Node, source: &str, violations: &mut Vec<RuleViolation>) {
if let Some(declarator) = node.child_by_field_name("declarator") {
self.check_declarator(&declarator, source, violations);
}
}
fn check_function_declarator(
&self,
node: &Node,
source: &str,
violations: &mut Vec<RuleViolation>,
) {
match node.kind() {
"identifier" => {
let name = get_node_text(node, source);
if self.is_reserved_function(&name) {
violations.push(RuleViolation {
rule_id: "DCL37-C".to_string(),
severity: Severity::Medium,
line: node.start_position().row + 1,
column: node.start_position().column + 1,
message: format!(
"Reserved function name '{}' should not be redefined",
name
),
file_path: String::new(),
suggestion: Some("Use a different function name".to_string()),
requires_manual_review: Some(false),
});
}
if self.is_reserved_identifier(&name) {
violations.push(RuleViolation {
rule_id: "DCL37-C".to_string(),
severity: Severity::Medium,
line: node.start_position().row + 1,
column: node.start_position().column + 1,
message: format!(
"Reserved identifier '{}' should not be used as function name",
name
),
file_path: String::new(),
suggestion: Some("Use a non-reserved function name".to_string()),
requires_manual_review: Some(false),
});
}
}
"function_declarator" => {
if let Some(declarator) = node.child_by_field_name("declarator") {
self.check_function_declarator(&declarator, source, violations);
}
}
"pointer_declarator" => {
if let Some(declarator) = node.child_by_field_name("declarator") {
self.check_function_declarator(&declarator, source, violations);
}
}
_ => {}
}
}
fn check_file_scope_declarator(
&self,
node: &Node,
source: &str,
violations: &mut Vec<RuleViolation>,
) {
match node.kind() {
"identifier" => {
let name = get_node_text(node, source);
if self.has_file_scope_reserved_prefix(&name) {
violations.push(RuleViolation {
rule_id: "DCL37-C".to_string(),
severity: Severity::Medium,
line: node.start_position().row + 1,
column: node.start_position().column + 1,
message: format!(
"File-scope identifier '{}' should not start with underscore",
name
),
file_path: String::new(),
suggestion: Some(
"Remove the leading underscore or rename the identifier".to_string(),
),
requires_manual_review: Some(false),
});
}
if self.is_reserved_identifier(&name) {
violations.push(RuleViolation {
rule_id: "DCL37-C".to_string(),
severity: Severity::Medium,
line: node.start_position().row + 1,
column: node.start_position().column + 1,
message: format!("Reserved identifier '{}' should not be used", name),
file_path: String::new(),
suggestion: Some("Use a non-reserved identifier".to_string()),
requires_manual_review: Some(false),
});
}
}
"pointer_declarator" | "array_declarator" | "function_declarator" => {
for child in node.named_children(&mut node.walk()) {
self.check_file_scope_declarator(&child, source, violations);
}
}
_ => {}
}
}
fn check_declarator(&self, node: &Node, source: &str, violations: &mut Vec<RuleViolation>) {
match node.kind() {
"identifier" => {
let name = get_node_text(node, source);
if self.is_reserved_identifier(&name) {
violations.push(RuleViolation {
rule_id: "DCL37-C".to_string(),
severity: Severity::Medium,
line: node.start_position().row + 1,
column: node.start_position().column + 1,
message: format!("Reserved identifier '{}' should not be used", name),
file_path: String::new(),
suggestion: Some("Use a non-reserved identifier".to_string()),
requires_manual_review: Some(false),
});
}
}
"pointer_declarator" | "array_declarator" | "function_declarator" => {
for child in node.named_children(&mut node.walk()) {
self.check_declarator(&child, source, violations);
}
}
_ => {}
}
}
fn check_preproc_def(&self, node: &Node, source: &str, violations: &mut Vec<RuleViolation>) {
if let Some(name_node) = node.child_by_field_name("name") {
let name = get_node_text(&name_node, source);
if self.is_reserved_identifier(&name) {
violations.push(RuleViolation {
rule_id: "DCL37-C".to_string(),
severity: Severity::Medium,
line: name_node.start_position().row + 1,
column: name_node.start_position().column + 1,
message: format!(
"Reserved identifier '{}' should not be used as a macro name",
name
),
file_path: String::new(),
suggestion: Some("Use a non-reserved macro name".to_string()),
requires_manual_review: Some(false),
});
}
}
}
}
impl Default for Dcl37C {
fn default() -> Self {
Self::new()
}
}
impl CertRule for Dcl37C {
fn rule_id(&self) -> &'static str {
"DCL37-C"
}
fn description(&self) -> &'static str {
"Do not declare or define a reserved identifier"
}
fn severity(&self) -> Severity {
Severity::Medium
}
fn category(&self) -> RuleCategory {
RuleCategory::Recommendation
}
fn cert_id(&self) -> &'static str {
"DCL37-C"
}
fn check(&self, root_node: &Node, source: &str) -> Vec<RuleViolation> {
let mut violations = Vec::new();
self.check_node(root_node, source, &mut violations);
violations
}
}