use crate::manifest::{RuleCategory, Severity};
use crate::prelude::RuleViolation;
use crate::rules::cert_c::CertRule;
use crate::utility::cert_c::ast_utils::get_node_text;
use std::collections::HashSet;
use tree_sitter::Node;
pub struct Msc40C;
impl CertRule for Msc40C {
fn rule_id(&self) -> &'static str {
"MSC40-C"
}
fn cert_id(&self) -> &'static str {
"MSC40"
}
fn description(&self) -> &'static str {
"Do not violate constraints"
}
fn severity(&self) -> Severity {
Severity::Low
}
fn category(&self) -> RuleCategory {
RuleCategory::Rule
}
fn check(&self, node: &Node, source: &str) -> Vec<RuleViolation> {
let mut violations = Vec::new();
let mut static_identifiers = HashSet::new();
self.collect_static_identifiers(node, source, &mut static_identifiers);
self.check_node_with_statics(node, source, &static_identifiers, &mut violations);
violations
}
}
impl Msc40C {
fn collect_static_identifiers(&self, node: &Node, source: &str, statics: &mut HashSet<String>) {
if node.kind() == "declaration" {
let decl_text = get_node_text(node, source);
if decl_text.trim_start().starts_with("static") {
if let Some(declarator) = node.child_by_field_name("declarator") {
let name = self.extract_identifier_name(&declarator, source);
if !name.is_empty() {
statics.insert(name);
}
}
}
}
if node.kind() == "translation_unit" {
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
if child.kind() != "function_definition" {
self.collect_static_identifiers(&child, source, statics);
}
}
}
}
fn extract_identifier_name(&self, node: &Node, source: &str) -> String {
match node.kind() {
"identifier" => get_node_text(node, source).trim().to_string(),
"init_declarator" | "pointer_declarator" | "array_declarator" => {
if let Some(declarator) = node.child_by_field_name("declarator") {
return self.extract_identifier_name(&declarator, source);
}
if let Some(child) = node.child(0) {
return self.extract_identifier_name(&child, source);
}
String::new()
}
_ => {
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
if child.kind() == "identifier" {
return get_node_text(&child, source).trim().to_string();
}
}
String::new()
}
}
}
fn check_node_with_statics(
&self,
node: &Node,
source: &str,
static_identifiers: &HashSet<String>,
violations: &mut Vec<RuleViolation>,
) {
if node.kind() == "function_definition" {
self.check_inline_function_with_statics(node, source, static_identifiers, violations);
}
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
self.check_node_with_statics(&child, source, static_identifiers, violations);
}
}
fn check_inline_function_with_statics(
&self,
node: &Node,
source: &str,
static_identifiers: &HashSet<String>,
violations: &mut Vec<RuleViolation>,
) {
if !self.is_non_static_inline_function(node, source) {
return;
}
self.check_for_internal_static_declarations(node, source, violations);
self.check_for_static_references_with_set(node, source, static_identifiers, violations);
}
#[allow(dead_code)]
fn check_node(&self, node: &Node, source: &str, violations: &mut Vec<RuleViolation>) {
if node.kind() == "function_definition" {
self.check_inline_function(node, 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_inline_function(
&self,
node: &Node,
source: &str,
violations: &mut Vec<RuleViolation>,
) {
if !self.is_non_static_inline_function(node, source) {
return;
}
self.check_for_internal_static_declarations(node, source, violations);
self.check_for_static_references(node, source, violations);
}
fn is_non_static_inline_function(&self, node: &Node, source: &str) -> bool {
let func_text = get_node_text(node, source);
let has_inline = func_text.contains("inline");
let has_extern = func_text.contains("extern");
if has_inline {
if let Some(inline_pos) = func_text.find("inline") {
let before_inline = &func_text[..inline_pos];
if before_inline.contains("static") {
return false; }
}
return true;
}
if has_extern {
if !func_text.trim_start().starts_with("static") {
return true; }
}
false
}
fn check_for_internal_static_declarations(
&self,
node: &Node,
source: &str,
violations: &mut Vec<RuleViolation>,
) {
if let Some(body) = node.child_by_field_name("body") {
self.find_static_declarations(&body, source, violations);
}
}
fn find_static_declarations(
&self,
node: &Node,
source: &str,
violations: &mut Vec<RuleViolation>,
) {
if node.kind() == "declaration" {
let decl_text = get_node_text(node, source);
if decl_text.trim_start().starts_with("static") {
let start_point = node.start_position();
violations.push(RuleViolation {
rule_id: self.rule_id().to_string(),
severity: Severity::Low,
message: "Non-static inline function contains static variable declaration, violating C Standard constraint. Use external linkage or make the function 'static inline'.".to_string(),
file_path: String::new(),
line: start_point.row + 1,
column: start_point.column + 1,
suggestion: Some(
"Either remove 'static' from the variable declaration, make the function 'static inline', or remove 'inline' from the function.".to_string()
),
..Default::default()
});
}
}
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
self.find_static_declarations(&child, source, violations);
}
}
fn check_for_static_references_with_set(
&self,
node: &Node,
source: &str,
static_identifiers: &HashSet<String>,
violations: &mut Vec<RuleViolation>,
) {
if let Some(body) = node.child_by_field_name("body") {
self.find_static_references(&body, source, static_identifiers, violations);
}
}
fn find_static_references(
&self,
node: &Node,
source: &str,
static_identifiers: &HashSet<String>,
violations: &mut Vec<RuleViolation>,
) {
if node.kind() == "identifier" {
let name = get_node_text(node, source).trim().to_string();
if static_identifiers.contains(&name) {
if let Some(parent) = node.parent() {
if parent.kind() != "init_declarator" && !parent.kind().contains("declarator") {
let start_point = node.start_position();
violations.push(RuleViolation {
rule_id: self.rule_id().to_string(),
severity: Severity::Low,
message: format!(
"Non-static inline function references static identifier '{}' which has internal linkage. This violates C Standard constraint.",
name
),
file_path: String::new(),
line: start_point.row + 1,
column: start_point.column + 1,
suggestion: Some(
"Either remove 'static' from the referenced identifier, make the function 'static inline', or remove 'inline' from the function.".to_string()
),
..Default::default()
});
return; }
}
}
}
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
self.find_static_references(&child, source, static_identifiers, violations);
}
}
#[allow(dead_code)]
fn check_for_static_references(
&self,
node: &Node,
source: &str,
violations: &mut Vec<RuleViolation>,
) {
let _ = (node, source, violations);
}
}