use super::types::{Confidence, NodeKind, Severity, Visibility};
#[derive(Debug, Clone, Default)]
pub struct StructuralSignals {
pub visibility: Option<Visibility>,
pub in_degree: usize,
pub out_degree: usize,
pub is_entry_point: bool,
pub lines_of_code: usize,
pub node_kind: Option<NodeKind>,
pub pagerank: Option<f64>,
pub is_test: bool,
}
#[derive(Debug, Clone, Default)]
pub struct PatternSignals {
pub known_fp_indicators: bool,
pub is_placeholder: bool,
pub dynamic_dispatch_indicators: bool,
pub user_input_nearby: bool,
pub is_test_file: bool,
pub is_in_comment: bool,
pub sibling_findings: usize,
}
#[derive(Debug, Clone)]
pub struct PriorityScore {
pub score: f64,
pub severity: Severity,
pub confidence: Confidence,
pub explanation: String,
}
pub fn compute_priority(
base_severity: Severity,
base_confidence: Confidence,
structural: &StructuralSignals,
pattern: &PatternSignals,
) -> PriorityScore {
let mut severity_num = severity_to_f64(base_severity);
let mut confidence_num = confidence_to_f64(base_confidence);
let mut reasons = Vec::new();
if pattern.known_fp_indicators || pattern.is_placeholder {
confidence_num *= 0.3;
reasons.push("placeholder/FP indicators detected");
}
if pattern.is_in_comment {
confidence_num *= 0.1;
reasons.push("in comment");
}
if pattern.is_test_file {
severity_num *= 0.5;
reasons.push("in test file");
}
if pattern.user_input_nearby {
confidence_num = (confidence_num * 1.3).min(1.0);
reasons.push("user input nearby");
}
if pattern.dynamic_dispatch_indicators {
confidence_num *= 0.5;
reasons.push("dynamic dispatch possible");
}
if pattern.sibling_findings > 5 {
confidence_num *= 0.8;
reasons.push("many similar findings (possible FP pattern)");
}
if structural.is_entry_point {
severity_num = (severity_num * 1.2).min(1.0);
reasons.push("entry point");
}
if let Some(pr) = structural.pagerank {
if pr > 0.05 {
severity_num = (severity_num * 1.1).min(1.0);
reasons.push("high centrality");
}
}
if structural.visibility == Some(Visibility::Private) && structural.in_degree == 0 {
confidence_num = (confidence_num * 1.2).min(1.0);
reasons.push("private with no callers");
}
if structural.is_test {
severity_num *= 0.5;
}
let score = (severity_num * 0.6 + confidence_num * 0.4) * 10.0;
let adjusted_severity = f64_to_severity(severity_num);
let adjusted_confidence = f64_to_confidence(confidence_num);
let explanation = if reasons.is_empty() {
"base score".to_string()
} else {
reasons.join("; ")
};
PriorityScore {
score,
severity: adjusted_severity,
confidence: adjusted_confidence,
explanation,
}
}
pub fn adjust_dead_code_confidence(base: Confidence, structural: &StructuralSignals) -> Confidence {
let mut conf = confidence_to_f64(base);
if structural.visibility == Some(Visibility::Private)
&& structural.in_degree == 0
&& !structural.is_entry_point
{
conf = (conf * 1.3).min(1.0);
}
if structural.visibility == Some(Visibility::Public) {
conf *= 0.6;
}
if let Some(pr) = structural.pagerank {
if pr > 0.05 {
conf *= 0.5;
}
}
if structural.lines_of_code > 100 {
conf = (conf * 1.1).min(1.0);
}
f64_to_confidence(conf)
}
pub fn adjust_security_confidence(base: Confidence, pattern: &PatternSignals) -> Confidence {
let mut conf = confidence_to_f64(base);
if pattern.known_fp_indicators || pattern.is_placeholder {
conf *= 0.3;
}
if pattern.user_input_nearby {
conf = (conf * 1.3).min(1.0);
}
if pattern.is_in_comment {
conf *= 0.1;
}
if pattern.is_test_file {
conf *= 0.5;
}
f64_to_confidence(conf)
}
pub fn clone_confidence(similarity: f64, lines: usize) -> Confidence {
let base: f64 = if similarity > 0.95 {
1.0
} else if similarity > 0.8 {
0.8
} else if similarity > 0.6 {
0.6
} else {
0.4
};
let size_factor: f64 = if lines > 50 {
1.1
} else if lines > 20 {
1.0
} else if lines > 10 {
0.9
} else {
0.7
};
f64_to_confidence((base * size_factor).min(1.0))
}
fn severity_to_f64(s: Severity) -> f64 {
match s {
Severity::Critical => 1.0,
Severity::High => 0.8,
Severity::Medium => 0.6,
Severity::Low => 0.4,
Severity::Info => 0.2,
}
}
fn f64_to_severity(v: f64) -> Severity {
if v >= 0.9 {
Severity::Critical
} else if v >= 0.7 {
Severity::High
} else if v >= 0.5 {
Severity::Medium
} else if v >= 0.3 {
Severity::Low
} else {
Severity::Info
}
}
fn confidence_to_f64(c: Confidence) -> f64 {
match c {
Confidence::Certain => 1.0,
Confidence::High => 0.8,
Confidence::Medium => 0.6,
Confidence::Low => 0.3,
}
}
fn f64_to_confidence(v: f64) -> Confidence {
if v >= 0.9 {
Confidence::Certain
} else if v >= 0.7 {
Confidence::High
} else if v >= 0.45 {
Confidence::Medium
} else {
Confidence::Low
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_compute_priority_base() {
let structural = StructuralSignals::default();
let pattern = PatternSignals::default();
let score = compute_priority(Severity::High, Confidence::High, &structural, &pattern);
assert!(score.score > 5.0);
assert_eq!(score.severity, Severity::High);
}
#[test]
fn test_placeholder_lowers_confidence() {
let structural = StructuralSignals::default();
let pattern = PatternSignals {
is_placeholder: true,
..Default::default()
};
let score = compute_priority(Severity::High, Confidence::High, &structural, &pattern);
assert!(score.confidence < Confidence::High);
}
#[test]
fn test_dead_code_private_high_confidence() {
let structural = StructuralSignals {
visibility: Some(Visibility::Private),
in_degree: 0,
is_entry_point: false,
..Default::default()
};
let conf = adjust_dead_code_confidence(Confidence::High, &structural);
assert!(conf >= Confidence::High);
}
#[test]
fn test_dead_code_public_lower_confidence() {
let structural = StructuralSignals {
visibility: Some(Visibility::Public),
in_degree: 0,
is_entry_point: false,
..Default::default()
};
let conf = adjust_dead_code_confidence(Confidence::High, &structural);
assert!(conf <= Confidence::High);
}
#[test]
fn test_clone_confidence_high_similarity() {
let conf = clone_confidence(0.98, 30);
assert!(conf >= Confidence::High);
}
#[test]
fn test_clone_confidence_low_similarity() {
let conf = clone_confidence(0.5, 5);
assert!(conf <= Confidence::Medium);
}
#[test]
fn test_security_confidence_with_user_input() {
let pattern = PatternSignals {
user_input_nearby: true,
..Default::default()
};
let conf = adjust_security_confidence(Confidence::Medium, &pattern);
assert!(conf >= Confidence::Medium);
}
}