use super::super::{CertRule, RuleViolation};
use crate::manifest::{RuleCategory, Severity};
use crate::utility::cert_c::ast_utils;
use std::collections::HashMap;
use tree_sitter::Node;
pub struct Arr37C;
impl CertRule for Arr37C {
fn rule_id(&self) -> &'static str {
"ARR37-C"
}
fn description(&self) -> &'static str {
"Do not add or subtract an integer to a pointer to a non-array object"
}
fn severity(&self) -> Severity {
Severity::High
}
fn category(&self) -> RuleCategory {
RuleCategory::Rule
}
fn cert_id(&self) -> &'static str {
"ARR37-C"
}
fn check(&self, node: &Node, source: &str) -> Vec<RuleViolation> {
let mut violations = Vec::new();
let mut analyzer = NonArrayPointerAnalyzer::new();
analyzer.collect_variable_info(node, source);
self.check_node(node, source, &analyzer, &mut violations);
violations
}
}
impl Arr37C {
fn check_node(
&self,
node: &Node,
source: &str,
analyzer: &NonArrayPointerAnalyzer,
violations: &mut Vec<RuleViolation>,
) {
let node_text = &source[node.start_byte()..node.end_byte()];
match node.kind() {
"binary_expression" => {
self.check_pointer_arithmetic(node, source, analyzer, violations);
}
"update_expression" => {
self.check_pointer_increment_decrement(node, source, analyzer, violations);
}
"assignment_expression"
if (node_text.contains("+=") || node_text.contains("-=")) => {
self.check_compound_assignment(node, source, analyzer, violations);
}
"subscript_expression" => {
self.check_subscript_on_non_array(node, source, analyzer, violations);
}
"for_statement" => {
self.check_for_loop_pointer_arithmetic(node, source, analyzer, violations);
}
_ => {}
}
for i in 0..node.child_count() {
if let Some(child) = node.child(i) {
self.check_node(&child, source, analyzer, violations);
}
}
}
fn check_pointer_arithmetic(
&self,
node: &Node,
source: &str,
analyzer: &NonArrayPointerAnalyzer,
violations: &mut Vec<RuleViolation>,
) {
if let Some(operator) = get_operator(node, source) {
match operator.as_str() {
"+" | "-" => {
if let (Some(left), Some(right)) = (
node.child_by_field_name("left"),
node.child_by_field_name("right"),
) {
if self.is_pointer_arithmetic(&left, &right, source, analyzer) {
let pointer_name = self.get_pointer_name(&left, source);
if analyzer.is_ambiguous_parameter(&pointer_name)
|| analyzer.is_unknown_pointer(&pointer_name)
{
} else if analyzer.is_struct_member_pointer(&pointer_name) {
let start_point = node.start_position();
violations.push(RuleViolation {
rule_id: self.rule_id().to_string(),
severity: Severity::Critical,
message: format!(
"Pointer arithmetic on struct member pointer '{}'. Pointer arithmetic on struct members is undefined behavior",
pointer_name
),
file_path: String::new(),
line: start_point.row + 1,
column: start_point.column + 1,
suggestion: Some("Do not perform pointer arithmetic across struct members".to_string()),
..Default::default()
});
} else if analyzer.is_non_array_pointer(&pointer_name) {
let start_point = node.start_position();
violations.push(RuleViolation {
rule_id: self.rule_id().to_string(),
severity: Severity::High,
message: format!(
"Pointer arithmetic on non-array pointer '{}'. Only perform arithmetic on array pointers",
pointer_name
),
file_path: String::new(),
line: start_point.row + 1,
column: start_point.column + 1,
suggestion: Some("Use array indexing or ensure pointer refers to an array".to_string()),
..Default::default()
});
}
}
}
}
_ => {}
}
}
}
fn check_pointer_increment_decrement(
&self,
node: &Node,
source: &str,
analyzer: &NonArrayPointerAnalyzer,
violations: &mut Vec<RuleViolation>,
) {
if let Some(argument) = node.child_by_field_name("argument") {
let pointer_name = self.get_pointer_name(&argument, source);
let start_point = node.start_position();
let op_text = &source[node.start_byte()..node.end_byte()];
if analyzer.is_ambiguous_parameter(&pointer_name)
|| analyzer.is_unknown_pointer(&pointer_name)
{
} else if analyzer.is_struct_member_pointer(&pointer_name) {
violations.push(RuleViolation {
rule_id: self.rule_id().to_string(),
severity: Severity::Critical,
message: format!(
"Increment/decrement operation '{}' on struct member pointer '{}'. Pointer arithmetic on struct members is undefined behavior",
op_text, pointer_name
),
file_path: String::new(),
line: start_point.row + 1,
column: start_point.column + 1,
suggestion: Some("Do not perform pointer arithmetic across struct members".to_string()),
..Default::default()
});
} else if analyzer.is_non_array_pointer(&pointer_name) {
violations.push(RuleViolation {
rule_id: self.rule_id().to_string(),
severity: Severity::High,
message: format!(
"Increment/decrement operation '{}' on non-array pointer '{}'",
op_text, pointer_name
),
file_path: String::new(),
line: start_point.row + 1,
column: start_point.column + 1,
suggestion: Some(
"Use array indexing or ensure pointer refers to an array".to_string(),
),
..Default::default()
});
}
}
}
fn check_compound_assignment(
&self,
node: &Node,
source: &str,
analyzer: &NonArrayPointerAnalyzer,
violations: &mut Vec<RuleViolation>,
) {
if let (Some(left), Some(_right)) = (
node.child_by_field_name("left"),
node.child_by_field_name("right"),
) {
let full_text = &source[node.start_byte()..node.end_byte()];
if full_text.contains("+=") || full_text.contains("-=") {
let pointer_name = self.get_pointer_name(&left, source);
let start_point = node.start_position();
let op_text = &source[node.start_byte()..node.end_byte()];
if analyzer.is_ambiguous_parameter(&pointer_name)
|| analyzer.is_unknown_pointer(&pointer_name)
{
} else if analyzer.is_struct_member_pointer(&pointer_name) {
violations.push(RuleViolation {
rule_id: self.rule_id().to_string(),
severity: Severity::Critical,
message: format!(
"Compound assignment '{}' on struct member pointer '{}'. Pointer arithmetic on struct members is undefined behavior",
op_text, pointer_name
),
file_path: String::new(),
line: start_point.row + 1,
column: start_point.column + 1,
suggestion: Some("Do not perform pointer arithmetic across struct members".to_string()),
..Default::default()
});
} else if analyzer.is_non_array_pointer(&pointer_name) {
violations.push(RuleViolation {
rule_id: self.rule_id().to_string(),
severity: Severity::High,
message: format!(
"Compound assignment '{}' on non-array pointer '{}'",
op_text, pointer_name
),
file_path: String::new(),
line: start_point.row + 1,
column: start_point.column + 1,
suggestion: Some(
"Use array indexing or ensure pointer refers to an array".to_string(),
),
..Default::default()
});
}
}
}
}
fn check_subscript_on_non_array(
&self,
node: &Node,
source: &str,
analyzer: &NonArrayPointerAnalyzer,
violations: &mut Vec<RuleViolation>,
) {
if let Some(argument) = node.child_by_field_name("argument") {
let pointer_name = self.get_pointer_name(&argument, source);
if pointer_name != "unknown" && !pointer_name.is_empty() {
let start_point = node.start_position();
let subscript_text = &source[node.start_byte()..node.end_byte()];
if analyzer.is_ambiguous_parameter(&pointer_name)
|| analyzer.is_unknown_pointer(&pointer_name)
{
} else if analyzer.is_struct_member_pointer(&pointer_name) {
violations.push(RuleViolation {
rule_id: self.rule_id().to_string(),
severity: Severity::Critical,
message: format!(
"Subscript operation '{}' on struct member pointer '{}'. Pointer arithmetic on struct members is undefined behavior",
subscript_text, pointer_name
),
file_path: String::new(),
line: start_point.row + 1,
column: start_point.column + 1,
suggestion: Some("Do not perform pointer arithmetic across struct members".to_string()),
..Default::default()
});
} else if analyzer.is_non_array_pointer(&pointer_name) {
violations.push(RuleViolation {
rule_id: self.rule_id().to_string(),
severity: Severity::High,
message: format!(
"Subscript operation '{}' on non-array pointer '{}'. Subscript notation implies array access",
subscript_text, pointer_name
),
file_path: String::new(),
line: start_point.row + 1,
column: start_point.column + 1,
suggestion: Some("Only use subscript notation on array pointers, not pointers to single objects".to_string()),
..Default::default()
});
}
}
}
}
fn check_for_loop_pointer_arithmetic(
&self,
node: &Node,
source: &str,
analyzer: &NonArrayPointerAnalyzer,
violations: &mut Vec<RuleViolation>,
) {
if let Some(update) = node.child_by_field_name("update") {
if update.kind() == "update_expression" {
if let Some(argument) = update.child_by_field_name("argument") {
let pointer_name = self.get_pointer_name(&argument, source);
if analyzer.is_struct_member_pointer(&pointer_name) {
let start_point = node.start_position();
violations.push(RuleViolation {
rule_id: self.rule_id().to_string(),
severity: Severity::Critical,
message: format!(
"Pointer arithmetic in loop on struct member pointer '{}'. Structure members are not guaranteed to be contiguous",
pointer_name
),
file_path: String::new(),
line: start_point.row + 1,
column: start_point.column + 1,
suggestion: Some("Access struct members individually or use an array within the struct".to_string()),
..Default::default()
});
}
}
}
}
}
fn is_pointer_arithmetic(
&self,
left: &Node,
right: &Node,
source: &str,
analyzer: &NonArrayPointerAnalyzer,
) -> bool {
let left_text = &source[left.start_byte()..left.end_byte()];
let right_text = &source[right.start_byte()..right.end_byte()];
let left_is_pointer =
left.kind() == "identifier" && analyzer.is_pointer_variable(left_text);
let right_is_integer =
right_text.chars().all(|c| c.is_ascii_digit()) || right.kind() == "number_literal";
left_is_pointer && right_is_integer
}
fn get_pointer_name(&self, node: &Node, source: &str) -> String {
match node.kind() {
"identifier" => source[node.start_byte()..node.end_byte()].to_string(),
_ => "unknown".to_string(),
}
}
}
struct NonArrayPointerAnalyzer {
variable_types: HashMap<String, VariableType>,
struct_member_pointers: HashMap<String, String>,
}
#[derive(Debug, Clone)]
enum VariableType {
Array,
NonArray,
StructMemberPointer,
AmbiguousParameter,
Unknown,
}
impl NonArrayPointerAnalyzer {
fn new() -> Self {
Self {
variable_types: HashMap::new(),
struct_member_pointers: HashMap::new(),
}
}
fn collect_variable_info(&mut self, node: &Node, source: &str) {
match node.kind() {
"declaration" => {
self.process_declaration(node, source);
}
"assignment_expression" => {
self.process_assignment(node, source);
}
"function_definition" => {
self.process_function_definition(node, source);
}
_ => {}
}
for i in 0..node.child_count() {
if let Some(child) = node.child(i) {
self.collect_variable_info(&child, source);
}
}
}
fn process_declaration(&mut self, node: &Node, source: &str) {
for i in 0..node.child_count() {
if let Some(child) = node.child(i) {
if child.kind() == "init_declarator" {
if let Some(declarator) = child.child_by_field_name("declarator") {
let var_name =
ast_utils::get_identifier_from_declarator(&declarator, source);
let var_type = if declarator.kind() == "array_declarator" {
Some(VariableType::Array)
} else if declarator.kind() == "pointer_declarator" {
if let Some(value) = child.child_by_field_name("value") {
Some(self.analyze_initializer_type(&value, source))
} else {
Some(VariableType::Unknown)
}
} else {
None };
if !var_name.is_empty() {
if let Some(var_type) = var_type {
self.variable_types.insert(var_name, var_type);
}
}
}
} else if child.kind() == "declarator"
|| child.kind() == "array_declarator"
|| child.kind() == "pointer_declarator"
{
let var_name = ast_utils::get_identifier_from_declarator(&child, source);
if !var_name.is_empty() {
let var_type = if child.kind() == "array_declarator" {
Some(VariableType::Array)
} else if child.kind() == "pointer_declarator" {
Some(VariableType::Unknown)
} else {
None
};
if let Some(var_type) = var_type {
self.variable_types.insert(var_name, var_type);
}
}
}
}
}
}
fn process_assignment(&mut self, node: &Node, source: &str) {
let node_text = &source[node.start_byte()..node.end_byte()];
if node_text.contains("+=")
|| node_text.contains("-=")
|| node_text.contains("*=")
|| node_text.contains("/=")
{
return;
}
if let (Some(left), Some(right)) = (
node.child_by_field_name("left"),
node.child_by_field_name("right"),
) {
if left.kind() == "identifier" {
let var_name = source[left.start_byte()..left.end_byte()].to_string();
if right.kind() == "binary_expression" {
return;
}
let var_type = self.analyze_initializer_type(&right, source);
self.variable_types.insert(var_name.clone(), var_type);
if right.kind() == "unary_expression" {
if let Some(argument) = right.child_by_field_name("argument") {
if argument.kind() == "field_expression" {
self.struct_member_pointers
.insert(var_name, "struct_member".to_string());
}
}
}
}
}
}
fn process_function_definition(&mut self, node: &Node, source: &str) {
if let Some(declarator) = node.child_by_field_name("declarator") {
if let Some(parameters) = declarator.child_by_field_name("parameters") {
let mut pointer_params = Vec::new();
for i in 0..parameters.child_count() {
if let Some(param) = parameters.child(i) {
if param.kind() == "parameter_declaration" {
if let Some(param_declarator) = param.child_by_field_name("declarator")
{
let param_name = ast_utils::get_identifier_from_declarator(
¶m_declarator,
source,
);
if !param_name.is_empty() {
if param_declarator.kind() == "array_declarator" {
self.variable_types.insert(param_name, VariableType::Array);
} else if param_declarator.kind() == "pointer_declarator" {
pointer_params.push(param_name);
}
}
}
}
}
}
for param_name in pointer_params {
self.variable_types
.insert(param_name, VariableType::AmbiguousParameter);
}
}
}
}
fn analyze_initializer_type(&self, node: &Node, source: &str) -> VariableType {
match node.kind() {
"identifier" => {
let name = source[node.start_byte()..node.end_byte()].to_string();
if let Some(existing_type) = self.variable_types.get(&name) {
existing_type.clone()
} else {
VariableType::Unknown
}
}
"unary_expression" | "pointer_expression" => {
if let Some(argument) = node.child_by_field_name("argument") {
match argument.kind() {
"identifier" => VariableType::NonArray, "subscript_expression" => VariableType::NonArray, "field_expression" => VariableType::StructMemberPointer, _ => VariableType::Unknown,
}
} else {
VariableType::Unknown
}
}
"subscript_expression" => VariableType::Array,
"cast_expression" => {
if let Some(value) = node.child_by_field_name("value") {
self.analyze_initializer_type(&value, source)
} else {
VariableType::NonArray
}
}
"call_expression" => {
self.analyze_allocation_call(node, source)
}
_ => VariableType::Unknown,
}
}
fn analyze_allocation_call(&self, node: &Node, source: &str) -> VariableType {
if let Some(function) = node.child_by_field_name("function") {
let func_name = source[function.start_byte()..function.end_byte()].to_string();
match func_name.as_str() {
"malloc" | "realloc" => {
if let Some(arguments) = node.child_by_field_name("arguments") {
let arg_text =
source[arguments.start_byte()..arguments.end_byte()].to_string();
if arg_text.contains('*') {
VariableType::Array
} else {
VariableType::NonArray
}
} else {
VariableType::NonArray
}
}
"calloc" => {
if let Some(arguments) = node.child_by_field_name("arguments") {
let arg_text =
source[arguments.start_byte()..arguments.end_byte()].to_string();
let cleaned = arg_text.trim_matches(&['(', ')', ' '][..]);
let args: Vec<&str> = cleaned.split(',').map(|s| s.trim()).collect();
if !args.is_empty() && args[0] == "1" {
VariableType::NonArray
} else {
VariableType::Array
}
} else {
VariableType::Array
}
}
"alloca" | "ALLOCA" | "_alloca" | "_malloca" => VariableType::Array,
"aligned_alloc" => {
if let Some(arguments) = node.child_by_field_name("arguments") {
let arg_text =
source[arguments.start_byte()..arguments.end_byte()].to_string();
if arg_text.contains('*') {
VariableType::Array
} else {
VariableType::NonArray
}
} else {
VariableType::NonArray
}
}
_ => VariableType::Unknown,
}
} else {
VariableType::Unknown
}
}
fn is_non_array_pointer(&self, var_name: &str) -> bool {
matches!(
self.variable_types.get(var_name),
Some(VariableType::NonArray)
)
}
fn is_struct_member_pointer(&self, var_name: &str) -> bool {
self.struct_member_pointers.contains_key(var_name)
|| matches!(
self.variable_types.get(var_name),
Some(VariableType::StructMemberPointer)
)
}
fn is_ambiguous_parameter(&self, var_name: &str) -> bool {
matches!(
self.variable_types.get(var_name),
Some(VariableType::AmbiguousParameter)
)
}
fn is_unknown_pointer(&self, var_name: &str) -> bool {
matches!(
self.variable_types.get(var_name),
Some(VariableType::Unknown)
)
}
fn is_pointer_variable(&self, var_name: &str) -> bool {
self.variable_types.contains_key(var_name)
}
}
fn get_operator(node: &Node, source: &str) -> Option<String> {
for i in 0..node.child_count() {
if let Some(child) = node.child(i) {
let text = source[child.start_byte()..child.end_byte()].to_string();
if matches!(text.as_str(), "+" | "-") {
return Some(text);
}
}
}
None
}