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 tree_sitter::Node;
pub struct Str11C;
impl CertRule for Str11C {
fn rule_id(&self) -> &'static str {
"STR11-C"
}
fn cert_id(&self) -> &'static str {
"STR11"
}
fn description(&self) -> &'static str {
"Do not specify the bound of a character array initialized with a string literal"
}
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();
self.check_declarations(node, source, &mut violations);
violations
}
}
impl Str11C {
fn check_declarations(&self, node: &Node, source: &str, violations: &mut Vec<RuleViolation>) {
if node.kind() == "declaration" {
self.check_declaration_node(node, source, violations);
}
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
self.check_declarations(&child, source, violations);
}
}
fn check_declaration_node(
&self,
node: &Node,
source: &str,
violations: &mut Vec<RuleViolation>,
) {
if let Some(type_node) = node.child_by_field_name("type") {
let type_text = get_node_text(&type_node, source).trim();
if !type_text.contains("char") {
return;
}
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
if child.kind() == "init_declarator" {
self.check_init_declarator(&child, source, violations);
}
}
}
}
fn check_init_declarator(
&self,
node: &Node,
source: &str,
violations: &mut Vec<RuleViolation>,
) {
if let Some(declarator) = node.child_by_field_name("declarator") {
if declarator.kind() == "array_declarator" {
if let Some(size_node) = declarator.child_by_field_name("size") {
let size_text = get_node_text(&size_node, source).trim();
if let Ok(array_size) = size_text.parse::<usize>() {
if let Some(value) = node.child_by_field_name("value") {
if value.kind() == "string_literal" {
let literal_text = get_node_text(&value, source).trim();
let string_length = self.get_string_literal_length(literal_text);
if array_size <= string_length {
let start_point = node.start_position();
violations.push(RuleViolation {
rule_id: self.rule_id().to_string(),
severity: Severity::Low,
message: format!(
"Character array bound [{}] is too small for string literal {} (needs {} for null terminator). \
Omit the array bound to let the compiler allocate sufficient storage.",
array_size,
literal_text,
string_length + 1
),
file_path: String::new(),
line: start_point.row + 1,
column: start_point.column + 1,
suggestion: Some(
"Remove the array bound (use []) to let the compiler automatically size the array, \
or specify a bound larger than the string length to accommodate the null terminator."
.to_string(),
),
..Default::default()
});
}
}
}
}
}
}
}
}
fn get_string_literal_length(&self, literal: &str) -> usize {
let content = literal.trim_matches('"');
let mut length = 0;
let mut chars = content.chars();
while let Some(ch) = chars.next() {
if ch == '\\' {
if let Some(next_ch) = chars.next() {
match next_ch {
'n' | 't' | 'r' | '\\' | '"' | '\'' | '0' => {
length += 1;
}
'x' => {
chars.next(); chars.next(); length += 1;
}
_ => {
length += 1;
}
}
}
} else {
length += 1;
}
}
length
}
}