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
pub mod brules;
mod cert_c;
use crate::analyze::cfg::FunctionCfg;
use crate::analyze::context::ProjectContext;
use crate::analyze::value_range::RangeAnalysisResult;
use std::collections::HashMap;
use tree_sitter::Node;
pub trait CertRule {
fn rule_id(&self) -> &'static str;
fn description(&self) -> &'static str;
fn severity(&self) -> crate::manifest::Severity;
fn category(&self) -> crate::manifest::RuleCategory;
fn cert_id(&self) -> &'static str;
fn check(&self, node: &Node, source: &str) -> Vec<RuleViolation>;
/// Enhanced check that receives CFG data for flow-sensitive analysis.
/// Default implementation delegates to `check()`, ignoring the CFG.
/// Rules that benefit from CFG analysis can override this.
fn check_with_cfg(
&self,
node: &Node,
source: &str,
_cfg: Option<&FunctionCfg>,
) -> Vec<RuleViolation> {
self.check(node, source)
}
/// Inject cross-file context gathered by the pre-scan phase.
/// Default is a no-op; only rules that need cross-file data override this.
fn set_project_context(&self, _context: &ProjectContext) {}
/// Inject per-file function CFGs for flow-sensitive analysis.
/// Default is a no-op; only rules that need CFG data override this.
fn set_function_cfgs(&self, _cfgs: &HashMap<usize, FunctionCfg>) {}
/// Returns true if this rule applies to the given file path.
/// Default: applies to all files. Override for rules that are
/// specific to a file type (e.g. header-only rules like PRE06-C).
fn applies_to_file(&self, _file_path: &str) -> bool {
true
}
/// Inject pre-computed value-range analysis results for flow-sensitive
/// integer range checking. Default is a no-op; only rules that need
/// VRA data override this.
fn set_vra_results(&self, _results: &HashMap<usize, RangeAnalysisResult>) {}
/// Returns true if this rule uses value-range analysis.
/// Used to avoid computing VRA when no enabled rules need it.
fn needs_vra(&self) -> bool {
false
}
}
#[derive(Debug, Clone, serde::Serialize)]
pub struct RuleViolation {
pub rule_id: String,
pub severity: crate::manifest::Severity,
pub message: String,
pub file_path: String,
pub line: usize,
pub column: usize,
pub suggestion: Option<String>,
/// Indicates if this violation requires manual investigation by the user.
/// Used for ambiguous cases where the tool cannot definitively determine if it's a violation.
/// `None` or `Some(false)` means it's a definite violation.
/// `Some(true)` means it requires manual review.
#[doc(hidden)]
pub requires_manual_review: Option<bool>,
}
impl Default for RuleViolation {
fn default() -> Self {
Self {
rule_id: String::new(),
severity: crate::manifest::Severity::Low,
message: String::new(),
file_path: String::new(),
line: 0,
column: 0,
suggestion: None,
requires_manual_review: None,
}
}
}
impl RuleViolation {
/// Returns true if this violation requires manual review
pub fn needs_manual_review(&self) -> bool {
self.requires_manual_review.unwrap_or(false)
}
}
pub struct RuleRegistry {
rules: Vec<Box<dyn CertRule>>,
}
impl Default for RuleRegistry {
fn default() -> Self {
Self::new()
}
}
pub fn get_rule_description(registry: &RuleRegistry, rule_id: &str) -> String {
if let Some(rule) = registry.get_rule(rule_id) {
rule.description().to_string()
} else {
"Unknown rule".to_string()
}
}