use super::super::{CertRule, RuleViolation};
use crate::manifest::{RuleCategory, Severity};
use crate::utility::cert_c::ast_utils::get_node_text;
use std::collections::HashSet;
use tree_sitter::Node;
pub struct Exp40C;
impl CertRule for Exp40C {
fn rule_id(&self) -> &'static str {
"EXP40-C"
}
fn description(&self) -> &'static str {
"Do not modify constant objects"
}
fn severity(&self) -> Severity {
Severity::Low
}
fn category(&self) -> RuleCategory {
RuleCategory::Rule
}
fn cert_id(&self) -> &'static str {
"EXP40-C"
}
fn check(&self, node: &Node, source: &str) -> Vec<RuleViolation> {
let mut violations = Vec::new();
let mut const_vars = HashSet::new();
collect_const_vars(node, source, &mut const_vars);
check_node_recursive(node, source, &const_vars, &mut violations);
violations
}
}
fn collect_const_vars(node: &Node, source: &str, const_vars: &mut HashSet<String>) {
match node.kind() {
"declaration" => {
let decl_text = get_node_text(node, source);
if decl_text.contains("const") {
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") {
if let Some(name) = extract_var_name(&declarator, source) {
const_vars.insert(name);
}
}
} else if child.kind() == "pointer_declarator"
|| child.kind() == "identifier"
{
if let Some(name) = extract_var_name(&child, source) {
const_vars.insert(name);
}
}
}
}
}
}
"parameter_declaration" => {
let param_text = get_node_text(node, source);
if param_text.contains("const") {
if let Some(declarator) = node.child_by_field_name("declarator") {
if let Some(name) = extract_var_name(&declarator, source) {
const_vars.insert(name);
}
}
}
}
_ => {}
}
for i in 0..node.child_count() {
if let Some(child) = node.child(i) {
collect_const_vars(&child, source, const_vars);
}
}
}
fn extract_var_name(node: &Node, source: &str) -> Option<String> {
match node.kind() {
"identifier" => Some(get_node_text(node, source).to_string()),
"pointer_declarator" | "array_declarator" => {
if let Some(declarator) = node.child_by_field_name("declarator") {
return extract_var_name(&declarator, source);
}
for i in 0..node.child_count() {
if let Some(child) = node.child(i) {
if child.kind() == "identifier" {
return Some(get_node_text(&child, source).to_string());
}
}
}
None
}
_ => None,
}
}
fn check_node_recursive(
node: &Node,
source: &str,
const_vars: &HashSet<String>,
violations: &mut Vec<RuleViolation>,
) {
match node.kind() {
"assignment_expression" => {
check_assignment(node, source, const_vars, violations);
}
"init_declarator" => {
check_init_declarator(node, source, const_vars, violations);
}
"pointer_declarator" => {
check_pointer_assignment(node, source, violations);
}
_ => {}
}
for i in 0..node.child_count() {
if let Some(child) = node.child(i) {
check_node_recursive(&child, source, const_vars, violations);
}
}
}
fn check_assignment(
node: &Node,
source: &str,
_const_vars: &HashSet<String>,
violations: &mut Vec<RuleViolation>,
) {
if let (Some(left), Some(right)) = (
node.child_by_field_name("left"),
node.child_by_field_name("right"),
) {
let left_text = get_node_text(&left, source);
let right_text = get_node_text(&right, source);
if left_text.starts_with('*') {
let _ptr_var = left_text.trim_start_matches('*').trim();
if is_potentially_const_violating(&right, source) {
report_violation(
node,
source,
violations,
&format!(
"Potential modification of const object through pointer: '{}' = '{}'",
left_text, right_text
),
);
}
}
if left.kind() == "identifier" && right.kind() == "pointer_expression" {
if let Some(op) = right.child_by_field_name("operator") {
if get_node_text(&op, source) == "&" {
if is_const_pointer_to_pointer_var(&left_text, node, source) {
report_violation(
node,
source,
violations,
&format!(
"Assigning address of non-const pointer to const-qualified pointer-to-pointer: {} = {}",
left_text, right_text
),
);
}
}
}
}
}
}
fn is_const_pointer_to_pointer_var(var_name: &str, node: &Node, source: &str) -> bool {
let mut root = *node;
while let Some(parent) = root.parent() {
root = parent;
}
find_const_ptr_ptr_decl(&root, var_name, source)
}
fn find_const_ptr_ptr_decl(node: &Node, var_name: &str, source: &str) -> bool {
if node.kind() == "declaration" {
let decl_text = get_node_text(node, source);
if decl_text.contains("const") && decl_text.contains("**") && decl_text.contains(var_name) {
if let Some(const_pos) = decl_text.find("const") {
if let Some(ptr_ptr_pos) = decl_text.find("**") {
if const_pos < ptr_ptr_pos {
return true;
}
}
}
}
}
for i in 0..node.child_count() {
if let Some(child) = node.child(i) {
if find_const_ptr_ptr_decl(&child, var_name, source) {
return true;
}
}
}
false
}
fn check_init_declarator(
node: &Node,
source: &str,
const_vars: &HashSet<String>,
violations: &mut Vec<RuleViolation>,
) {
if let Some(declarator) = node.child_by_field_name("declarator") {
if let Some(value) = node.child_by_field_name("value") {
if is_pointer_declarator(&declarator) {
let decl_text = get_node_text(&declarator, source);
let value_text = get_node_text(&value, source);
let parent_has_const = node
.parent()
.filter(|p| p.kind() == "declaration")
.is_some_and(|decl| {
let mut cursor = decl.walk();
for child in decl.children(&mut cursor) {
if child.kind() == "type_qualifier" {
let q = get_node_text(&child, source);
if q == "const" {
return true;
}
}
}
false
});
if !parent_has_const
&& !contains_const_keyword(&declarator, source)
&& is_const_qualified(&value, source, const_vars)
{
report_violation(
node,
source,
violations,
&format!(
"Pointer to const assigned to non-const pointer without cast: {} = {}",
decl_text, value_text
),
);
}
}
}
}
}
fn check_pointer_assignment(node: &Node, source: &str, violations: &mut Vec<RuleViolation>) {
let node_text = get_node_text(node, source);
if node_text.contains("**") && node.parent().is_some() {
if let Some(parent) = node.parent() {
if parent.kind() == "init_declarator" {
if let Some(value) = parent.child_by_field_name("value") {
let value_text = get_node_text(&value, source);
if value_text.contains('&') && !contains_const_in_pointer_chain(node, source) {
check_pointer_to_pointer_const(&parent, source, violations);
}
}
}
}
}
}
fn is_pointer_declarator(node: &Node) -> bool {
node.kind() == "pointer_declarator" || (node.kind() == "declarator" && has_pointer_child(node))
}
fn has_pointer_child(node: &Node) -> bool {
for i in 0..node.child_count() {
if let Some(child) = node.child(i) {
if child.kind() == "pointer_declarator" || child.kind() == "*" {
return true;
}
if has_pointer_child(&child) {
return true;
}
}
}
false
}
fn contains_const_keyword(node: &Node, source: &str) -> bool {
let text = get_node_text(node, source);
if text.contains("const") {
return true;
}
for i in 0..node.child_count() {
if let Some(child) = node.child(i) {
if child.kind() == "type_qualifier" {
let qualifier_text = get_node_text(&child, source);
if qualifier_text == "const" {
return true;
}
}
if contains_const_keyword(&child, source) {
return true;
}
}
}
false
}
fn is_const_qualified(node: &Node, source: &str, const_vars: &HashSet<String>) -> bool {
match node.kind() {
"pointer_expression" => {
if let Some(argument) = node.child_by_field_name("argument") {
return is_const_qualified(&argument, source, const_vars);
}
}
"identifier" => {
let name = get_node_text(node, source);
return const_vars.contains(name);
}
_ => {
return contains_const_keyword(node, source);
}
}
false
}
fn is_potentially_const_violating(node: &Node, source: &str) -> bool {
if node.kind() == "pointer_expression" {
if let Some(operator) = node.child_by_field_name("operator") {
let op_text = get_node_text(&operator, source);
if op_text == "&" {
if let Some(argument) = node.child_by_field_name("argument") {
return contains_const_keyword(&argument, source);
}
}
}
}
contains_const_keyword(node, source)
}
fn contains_const_in_pointer_chain(node: &Node, source: &str) -> bool {
let mut current = *node;
while let Some(parent) = current.parent() {
if parent.kind() == "declaration" {
return contains_const_keyword(&parent, source);
}
current = parent;
}
false
}
fn check_pointer_to_pointer_const(node: &Node, source: &str, violations: &mut Vec<RuleViolation>) {
if let Some(declarator) = node.child_by_field_name("declarator") {
let decl_text = get_node_text(&declarator, source);
if let Some(value) = node.child_by_field_name("value") {
let value_text = get_node_text(&value, source);
if decl_text.contains("**") {
if value_text.contains('&') && !value_text.contains("const") {
report_violation(
node,
source,
violations,
&format!(
"Pointer-to-pointer assignment may allow const circumvention: {} = {}",
decl_text, value_text
),
);
}
}
}
}
}
fn report_violation(node: &Node, source: &str, violations: &mut Vec<RuleViolation>, message: &str) {
let start_point = node.start_position();
let node_text = get_node_text(node, source);
violations.push(RuleViolation {
rule_id: "EXP40-C".to_string(),
severity: Severity::Low,
message: format!("{}: '{}'", message, node_text),
file_path: String::new(),
line: start_point.row + 1,
column: start_point.column + 1,
suggestion: Some(
"Either remove const qualifier if the object should be modifiable, or use explicit casts to show intentional const removal"
.to_string(),
),
..Default::default()
});
}