use super::super::{CertRule, RuleViolation};
use crate::manifest::{RuleCategory, Severity};
use crate::utility::cert_c::ast_utils::get_node_text;
use tree_sitter::Node;
pub struct Msc38C;
impl Msc38C {
pub fn new() -> Self {
Self
}
const PROTECTED_IDENTIFIERS: [&'static str; 8] = [
"assert",
"errno",
"math_errhandling",
"setjmp",
"va_arg",
"va_copy",
"va_end",
"va_start",
];
fn is_protected_identifier(&self, identifier: &str) -> bool {
Self::PROTECTED_IDENTIFIERS.contains(&identifier)
}
fn check_preproc_undef(&self, node: &Node, source: &str, violations: &mut Vec<RuleViolation>) {
if node.kind() == "preproc_undef" {
if let Some(name_node) = node.child_by_field_name("name") {
let identifier = get_node_text(&name_node, source).trim();
if self.is_protected_identifier(identifier) {
violations.push(RuleViolation {
rule_id: self.rule_id().to_string(),
severity: self.severity(),
message: format!(
"Attempting to #undef protected identifier '{}'. This identifier must not have its macro definition suppressed as it may only be implemented as a macro.",
identifier
),
file_path: String::new(),
line: node.start_position().row + 1,
column: node.start_position().column + 1,
suggestion: Some(
format!("Remove the #undef directive for '{}'. If you need to use the underlying function, create a wrapper function instead.", identifier)
),
..Default::default()
});
}
}
}
}
fn check_parenthesized_identifier(
&self,
node: &Node,
source: &str,
violations: &mut Vec<RuleViolation>,
) {
if node.kind() == "pointer_expression" {
if let Some(operator_node) = node.child(0) {
if get_node_text(&operator_node, source).trim() == "&" {
if let Some(argument_node) = node.child_by_field_name("argument") {
if argument_node.kind() == "parenthesized_expression" {
for i in 0..argument_node.child_count() {
if let Some(child) = argument_node.child(i) {
if child.kind() == "identifier" {
let identifier = get_node_text(&child, source).trim();
if self.is_protected_identifier(identifier) {
violations.push(RuleViolation {
rule_id: self.rule_id().to_string(),
severity: self.severity(),
message: format!(
"Using '&({})' suppresses the macro definition for protected identifier '{}'. This results in undefined behavior.",
identifier, identifier
),
file_path: String::new(),
line: node.start_position().row + 1,
column: node.start_position().column + 1,
suggestion: Some(
format!("Create a wrapper function that calls '{}' and take the address of the wrapper instead.", identifier)
),
..Default::default()
});
}
}
}
}
}
}
}
}
}
}
fn check_manual_declaration(
&self,
node: &Node,
source: &str,
violations: &mut Vec<RuleViolation>,
) {
if node.kind() == "declaration" {
let has_extern = (0..node.child_count()).any(|i| {
if let Some(child) = node.child(i) {
if child.kind() == "storage_class_specifier" {
return get_node_text(&child, source).trim() == "extern";
}
}
false
});
if let Some(declarator) = node.child_by_field_name("declarator") {
let identifier = self.extract_identifier_from_declarator(&declarator, source);
if let Some(ident) = identifier {
if self.is_protected_identifier(&ident) {
let message = if has_extern {
format!(
"Manual 'extern' declaration of protected identifier '{}'. This identifier should only be accessed through the appropriate standard header.",
ident
)
} else {
format!(
"Manual declaration of protected identifier '{}'. This identifier should only be accessed through the appropriate standard header.",
ident
)
};
let suggestion = match ident.as_str() {
"errno" => "Include <errno.h> instead of manually declaring errno.",
"assert" => "Include <assert.h> instead of manually declaring assert.",
"setjmp" => "Include <setjmp.h> instead of manually declaring setjmp.",
"math_errhandling" => "Include <math.h> instead of manually declaring math_errhandling.",
"va_arg" | "va_copy" | "va_end" | "va_start" => "Include <stdarg.h> instead of manually declaring variadic argument macros.",
_ => "Include the appropriate standard header instead of manually declaring this identifier.",
};
violations.push(RuleViolation {
rule_id: self.rule_id().to_string(),
severity: self.severity(),
message,
file_path: String::new(),
line: node.start_position().row + 1,
column: node.start_position().column + 1,
suggestion: Some(suggestion.to_string()),
..Default::default()
});
}
}
}
}
}
fn extract_identifier_from_declarator(
&self,
declarator: &Node,
source: &str,
) -> Option<String> {
match declarator.kind() {
"identifier" => Some(get_node_text(declarator, source).trim().to_string()),
"pointer_declarator" | "array_declarator" | "function_declarator" => {
if let Some(decl_node) = declarator.child_by_field_name("declarator") {
return self.extract_identifier_from_declarator(&decl_node, source);
}
None
}
_ => {
for i in 0..declarator.child_count() {
if let Some(child) = declarator.child(i) {
if child.kind() == "identifier" {
return Some(get_node_text(&child, source).trim().to_string());
}
if let Some(ident) = self.extract_identifier_from_declarator(&child, source)
{
return Some(ident);
}
}
}
None
}
}
}
}
impl CertRule for Msc38C {
fn rule_id(&self) -> &'static str {
"MSC38-C"
}
fn description(&self) -> &'static str {
"Do not treat a predefined identifier as an object if it might only be implemented as a macro"
}
fn severity(&self) -> Severity {
Severity::Low
}
fn category(&self) -> RuleCategory {
RuleCategory::Rule
}
fn cert_id(&self) -> &'static str {
"MSC38-C"
}
fn check(&self, node: &Node, source: &str) -> Vec<RuleViolation> {
let mut violations = Vec::new();
self.check_node(node, source, &mut violations);
violations
}
}
impl Msc38C {
fn check_node(&self, node: &Node, source: &str, violations: &mut Vec<RuleViolation>) {
self.check_preproc_undef(node, source, violations);
self.check_parenthesized_identifier(node, source, violations);
self.check_manual_declaration(node, source, violations);
for i in 0..node.child_count() {
if let Some(child) = node.child(i) {
self.check_node(&child, source, violations);
}
}
}
}