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;
#[derive(Debug)]
#[allow(dead_code)]
pub struct Exp44C {
generic_macros: HashSet<String>,
}
impl Exp44C {
pub fn new() -> Self {
Exp44C {
generic_macros: HashSet::new(),
}
}
fn has_side_effect(&self, node: &Node) -> bool {
match node.kind() {
"update_expression" => true,
"assignment_expression" => true,
"call_expression" => true,
"compound_assignment_expression" => true,
_ => {
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
if self.has_side_effect(&child) {
return true;
}
}
false
}
}
}
fn check_sizeof_expression(&self, node: &Node, violations: &mut Vec<RuleViolation>) {
if node.kind() != "sizeof_expression" {
return;
}
if let Some(value) = node.child_by_field_name("value") {
if self.has_side_effect(&value) {
violations.push(RuleViolation {
rule_id: "EXP44-C".to_string(),
severity: Severity::Low,
line: value.start_position().row + 1,
column: value.start_position().column + 1,
message: "Side effect in sizeof operand will not be evaluated".to_string(),
file_path: String::new(),
suggestion: Some("Move side effects outside of sizeof operator".to_string()),
requires_manual_review: Some(false),
});
}
}
if let Some(type_node) = node.child_by_field_name("type") {
if self.has_side_effect(&type_node) {
violations.push(RuleViolation {
rule_id: "EXP44-C".to_string(),
severity: Severity::Low,
line: type_node.start_position().row + 1,
column: type_node.start_position().column + 1,
message: "Side effect in sizeof operand will not be evaluated".to_string(),
file_path: String::new(),
suggestion: Some("Move side effects outside of sizeof operator".to_string()),
requires_manual_review: Some(false),
});
}
}
}
fn check_alignof_expression(&self, node: &Node, violations: &mut Vec<RuleViolation>) {
if !node.kind().contains("alignof") && !node.kind().contains("Alignof") {
return;
}
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
if self.has_side_effect(&child) {
violations.push(RuleViolation {
rule_id: "EXP44-C".to_string(),
severity: Severity::Low,
line: child.start_position().row + 1,
column: child.start_position().column + 1,
message: "_Alignof operand is never evaluated; side effects will not occur"
.to_string(),
file_path: String::new(),
suggestion: Some("Move side effects outside of _Alignof operator".to_string()),
requires_manual_review: Some(false),
});
}
}
}
fn check_generic_expression(&self, node: &Node, violations: &mut Vec<RuleViolation>) {
if !node.kind().contains("generic") && !node.kind().contains("Generic") {
return;
}
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
if child.kind() == "identifier" || child.kind() == "(" {
continue;
}
if self.has_side_effect(&child) {
violations.push(RuleViolation {
rule_id: "EXP44-C".to_string(),
severity: Severity::Low,
line: child.start_position().row + 1,
column: child.start_position().column + 1,
message: "_Generic controlling expression is never evaluated; side effects will not occur".to_string(),
file_path: String::new(),
suggestion: Some(
"Move side effects outside of _Generic operator".to_string(),
),
requires_manual_review: Some(false),
});
}
break; }
}
fn collect_generic_macros(&self, node: &Node, source: &str, macros: &mut HashSet<String>) {
if node.kind() == "preproc_function_def" || node.kind() == "preproc_def" {
let text = get_node_text(node, source);
if text.contains("_Generic") {
if let Some(name_node) = node.child_by_field_name("name") {
let name = get_node_text(&name_node, source).trim().to_string();
macros.insert(name);
}
}
}
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
self.collect_generic_macros(&child, source, macros);
}
}
fn check_generic_macro_call(
&self,
node: &Node,
source: &str,
generic_macros: &HashSet<String>,
violations: &mut Vec<RuleViolation>,
) {
if node.kind() != "call_expression" {
return;
}
if let Some(func_node) = node.child_by_field_name("function") {
let func_name = get_node_text(&func_node, source).trim().to_string();
if generic_macros.contains(&func_name) {
if let Some(args_node) = node.child_by_field_name("arguments") {
let mut cursor = args_node.walk();
for child in args_node.children(&mut cursor) {
if child.kind() != "(" && child.kind() != ")" && child.kind() != "," {
if self.has_side_effect(&child) {
violations.push(RuleViolation {
rule_id: "EXP44-C".to_string(),
severity: Severity::Low,
line: child.start_position().row + 1,
column: child.start_position().column + 1,
message: format!(
"Side effect in macro '{}' argument that uses _Generic; expression may not be evaluated",
func_name
),
file_path: String::new(),
suggestion: Some(
"Move side effects outside of _Generic-containing macro call".to_string(),
),
requires_manual_review: Some(false),
});
}
}
}
}
}
}
}
fn traverse(
&self,
node: &Node,
source: &str,
generic_macros: &HashSet<String>,
violations: &mut Vec<RuleViolation>,
) {
self.check_sizeof_expression(node, violations);
self.check_alignof_expression(node, violations);
self.check_generic_expression(node, violations);
self.check_generic_macro_call(node, source, generic_macros, violations);
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
self.traverse(&child, source, generic_macros, violations);
}
}
}
impl CertRule for Exp44C {
fn rule_id(&self) -> &'static str {
"EXP44-C"
}
fn description(&self) -> &'static str {
"Do not rely on side effects in operands to sizeof, _Alignof, or _Generic"
}
fn category(&self) -> RuleCategory {
RuleCategory::Rule
}
fn severity(&self) -> Severity {
Severity::Low
}
fn cert_id(&self) -> &'static str {
"EXP44-C"
}
fn check(&self, root: &Node, source: &str) -> Vec<RuleViolation> {
let mut violations = Vec::new();
let mut generic_macros = HashSet::new();
self.collect_generic_macros(root, source, &mut generic_macros);
self.traverse(root, source, &generic_macros, &mut violations);
violations
}
}