use crate::manifest::{RuleCategory, Severity};
use crate::rules::{CertRule, RuleViolation};
use crate::utility::cert_c::ast_utils::get_node_text;
use lang_parsing_substrate::query;
use streaming_iterator::StreamingIterator;
use tree_sitter::{Node, Query, QueryCursor};
pub struct Exp42C;
impl CertRule for Exp42C {
fn rule_id(&self) -> &'static str {
"EXP42-C"
}
fn description(&self) -> &'static str {
"Do not compare padding data"
}
fn severity(&self) -> Severity {
Severity::Medium
}
fn category(&self) -> RuleCategory {
RuleCategory::Rule
}
fn cert_id(&self) -> &'static str {
"EXP42-C"
}
fn check(&self, node: &Node, source: &str) -> Vec<RuleViolation> {
let mut violations = Vec::new();
let query_str = r#"
(call_expression
function: (identifier) @func_name
arguments: (argument_list) @args
) @call
"#;
let language = crate::parser::c_language();
let query = Query::new(&language, query_str).expect("Invalid query");
let mut query_cursor = QueryCursor::new();
let mut matches = query_cursor.matches(&query, *node, source.as_bytes());
while let Some(m) = matches.next() {
let mut func_name_node = None;
let mut args_node = None;
let mut call_node = None;
for capture in m.captures {
let capture_name = &query.capture_names()[capture.index as usize];
match &**capture_name {
"func_name" => func_name_node = Some(capture.node),
"args" => args_node = Some(capture.node),
"call" => call_node = Some(capture.node),
_ => {}
}
}
if let (Some(func_node), Some(args), Some(call)) =
(func_name_node, args_node, call_node)
{
let func_name = get_node_text(&func_node, source);
if func_name == "memcmp" || func_name == "memcmp_s" {
if is_struct_comparison(&args, source) {
let start_pos = call.start_position();
violations.push(RuleViolation {
rule_id: "EXP42-C".to_string(),
severity: Severity::Medium,
message: format!(
"Comparing padding data using {}(). Padding bytes in structures have indeterminate values and should not be compared. Consider comparing struct members individually instead, or use #pragma pack to eliminate padding.",
func_name
),
file_path: String::new(), line: start_pos.row + 1,
column: start_pos.column + 1,
suggestion: Some(
"Compare struct members individually instead of using memcmp()".to_string()
),
requires_manual_review: None,
});
}
}
}
}
violations
}
}
fn is_struct_comparison(args_node: &Node, source: &str) -> bool {
let mut cursor = args_node.walk();
let mut arguments = Vec::new();
for child in args_node.children(&mut cursor) {
if child.kind() != "," && child.kind() != "(" && child.kind() != ")" {
arguments.push(child);
}
}
if arguments.len() < 3 {
return false;
}
let size_arg = arguments[2];
if has_sizeof_struct(&size_arg, source) {
return true;
}
for i in 0..2 {
if i < arguments.len() && looks_like_struct_pointer(&arguments[i], source) {
if looks_like_sizeof_usage(&size_arg, source) {
return true;
}
}
}
false
}
fn has_sizeof_struct(node: &Node, source: &str) -> bool {
let text = get_node_text(node, source);
if text.contains("sizeof") && text.contains("struct") {
return true;
}
query::find_first_descendant(*node, |n| {
if n.kind() != "sizeof_expression" {
return false;
}
let sizeof_text = get_node_text(&n, source);
if sizeof_text.contains("struct") {
return true;
}
let mut cursor = n.walk();
let has_struct_child = n
.children(&mut cursor)
.any(|c| c.kind() == "struct_specifier" || c.kind() == "type_identifier");
has_struct_child
})
.is_some()
}
fn looks_like_sizeof_usage(node: &Node, source: &str) -> bool {
let text = get_node_text(node, source);
text.contains("sizeof")
}
fn looks_like_struct_pointer(node: &Node, source: &str) -> bool {
query::find_first_descendant(*node, |n| {
if n.kind() == "cast_expression" {
let text = get_node_text(&n, source);
if text.contains("struct") {
return true;
}
}
if n.kind() == "unary_expression" {
let text = get_node_text(&n, source);
if text.starts_with('&') {
return true;
}
}
if n.kind() == "pointer_expression" {
return true;
}
false
})
.is_some()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_rule_id() {
let rule = Exp42C;
assert_eq!(rule.rule_id(), "EXP42-C");
}
#[test]
fn test_description() {
let rule = Exp42C;
assert_eq!(rule.description(), "Do not compare padding data");
}
}