1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2024 Ryan Urchick
use super::super::{CertRule, RuleViolation};
use crate::manifest::{RuleCategory, Severity};
use lang_parsing_substrate::query;
use tree_sitter::Node;
pub struct Pre12C;
impl CertRule for Pre12C {
fn rule_id(&self) -> &'static str {
"PRE12-C"
}
fn description(&self) -> &'static str {
"Do not define unsafe macros"
}
fn severity(&self) -> Severity {
Severity::Medium
}
fn category(&self) -> RuleCategory {
RuleCategory::Rule
}
fn cert_id(&self) -> &'static str {
"PRE12-C"
}
fn scan(&self, node: &Node, source: &str, violations: &mut Vec<RuleViolation>) {
self.check_node(node, source, violations);
}
}
impl Pre12C {
fn check_node(&self, node: &Node, source: &str, violations: &mut Vec<RuleViolation>) {
for n in query::find_descendants(*node, |_| true) {
// Look for preprocessor macro definitions
if n.kind() == "preproc_function_def" {
let text = n.utf8_text(source.as_bytes()).unwrap_or("");
// Skip macros using __extension__ (GCC extension that handles evaluation correctly)
if !text.contains("__extension__") {
// Check if macro uses parameters multiple times in definition
// Extract parameter names from #define NAME(param1, param2)
if let Some(params) = self.extract_macro_params(text) {
let definition = text.split(')').skip(1).collect::<String>();
// Check if any parameter appears more than once in the definition
for param in params {
// Count occurrences of parameter as standalone identifier
let mut count = 0;
for word in definition.split(|c: char| !c.is_alphanumeric() && c != '_')
{
if word == param {
count += 1;
}
}
if count > 1 {
violations.push(RuleViolation {
rule_id: self.rule_id().to_string(),
severity: self.severity(),
line: n.start_position().row + 1,
column: n.start_position().column + 1,
file_path: String::new(),
message: format!("Macro evaluates parameter '{}' multiple times; use inline function instead", param),
suggestion: Some("Replace macro with inline function to avoid multiple evaluation".to_string()),
requires_manual_review: None,
});
break; // Only report once per macro
}
}
}
}
}
// Also detect expanded macro patterns: expressions with multiple side-effects
// Pattern: (expr) ? -(expr) : (expr) where expr has side effects like ++n
if n.kind() == "assignment_expression" || n.kind() == "conditional_expression" {
let text = n.utf8_text(source.as_bytes()).unwrap_or("");
// Check for increment/decrement operators appearing multiple times
if text.contains("++") || text.contains("--") {
// Count occurrences of side-effect operators
let inc_count = text.matches("++").count() + text.matches("--").count();
// If there are 3+ occurrences, it's likely from macro expansion
if inc_count >= 3 && (text.contains('?') || text.contains(':')) {
violations.push(RuleViolation {
rule_id: self.rule_id().to_string(),
severity: self.severity(),
line: n.start_position().row + 1,
column: n.start_position().column + 1,
file_path: String::new(),
message: "Expression with multiple side-effects; likely from unsafe macro expansion".to_string(),
suggestion: Some("Avoid passing expressions with side effects to macros".to_string()),
requires_manual_review: None,
});
}
}
}
}
}
fn extract_macro_params(&self, text: &str) -> Option<Vec<String>> {
// Extract params from #define NAME(param1, param2) definition
if let Some(start) = text.find('(') {
if let Some(end) = text.find(')') {
let params_str = &text[start + 1..end];
let params: Vec<String> = params_str
.split(',')
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty())
.collect();
if !params.is_empty() {
return Some(params);
}
}
}
None
}
}