2026-02-24 23:44:07 -05:00
|
|
|
use super::dominators;
|
|
|
|
|
use super::{AnalysisContext, CfgAnalysis, CfgFinding, Confidence};
|
|
|
|
|
use crate::cfg::StmtKind;
|
|
|
|
|
use crate::labels::DataLabel;
|
|
|
|
|
use crate::patterns::Severity;
|
2026-02-25 04:02:11 -05:00
|
|
|
use std::collections::HashSet;
|
2026-02-24 23:44:07 -05:00
|
|
|
|
|
|
|
|
pub struct UnreachableCode;
|
|
|
|
|
|
2026-02-25 04:02:11 -05:00
|
|
|
/// Collect function names that appear as arguments to configured event handler calls.
|
|
|
|
|
fn event_handler_callbacks(ctx: &AnalysisContext) -> HashSet<String> {
|
|
|
|
|
let mut callbacks = HashSet::new();
|
|
|
|
|
let handlers = match ctx.analysis_rules {
|
|
|
|
|
Some(rules) if !rules.event_handlers.is_empty() => &rules.event_handlers,
|
|
|
|
|
_ => return callbacks,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
for idx in ctx.cfg.node_indices() {
|
|
|
|
|
let info = &ctx.cfg[idx];
|
|
|
|
|
if info.kind != StmtKind::Call {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if let Some(callee) = &info.callee {
|
|
|
|
|
let callee_lower = callee.to_ascii_lowercase();
|
|
|
|
|
let is_handler = handlers
|
|
|
|
|
.iter()
|
|
|
|
|
.any(|h| callee_lower.ends_with(&h.to_ascii_lowercase()));
|
|
|
|
|
if is_handler {
|
|
|
|
|
// The callback function is typically used within the call — any function
|
|
|
|
|
// that appears as `uses` of this call node is a potential callback.
|
|
|
|
|
for u in &info.uses {
|
|
|
|
|
callbacks.insert(u.clone());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
callbacks
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-24 23:44:07 -05:00
|
|
|
impl CfgAnalysis for UnreachableCode {
|
|
|
|
|
fn name(&self) -> &'static str {
|
|
|
|
|
"unreachable-code"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn run(&self, ctx: &AnalysisContext) -> Vec<CfgFinding> {
|
|
|
|
|
let reachable = dominators::reachable_set(ctx.cfg, ctx.entry);
|
2026-02-25 04:02:11 -05:00
|
|
|
let handler_callbacks = event_handler_callbacks(ctx);
|
2026-02-24 23:44:07 -05:00
|
|
|
let mut findings = Vec::new();
|
|
|
|
|
|
|
|
|
|
for idx in ctx.cfg.node_indices() {
|
|
|
|
|
if reachable.contains(&idx) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let info = &ctx.cfg[idx];
|
|
|
|
|
|
|
|
|
|
// Skip synthetic Entry/Exit nodes
|
|
|
|
|
if matches!(info.kind, StmtKind::Entry | StmtKind::Exit) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-25 04:02:11 -05:00
|
|
|
// Suppress findings for nodes inside event handler callbacks
|
|
|
|
|
if let Some(func_name) = &info.enclosing_func
|
|
|
|
|
&& handler_callbacks.contains(func_name)
|
|
|
|
|
{
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-24 23:44:07 -05:00
|
|
|
let (rule_id, title, severity) = match info.label {
|
|
|
|
|
Some(DataLabel::Sanitizer(_)) => (
|
|
|
|
|
"cfg-unreachable-sanitizer",
|
|
|
|
|
"Unreachable sanitizer",
|
|
|
|
|
Severity::Medium,
|
|
|
|
|
),
|
|
|
|
|
Some(DataLabel::Sink(_)) => {
|
|
|
|
|
("cfg-unreachable-sink", "Unreachable sink", Severity::Medium)
|
|
|
|
|
}
|
|
|
|
|
Some(DataLabel::Source(_)) => (
|
|
|
|
|
"cfg-unreachable-source",
|
|
|
|
|
"Unreachable source",
|
|
|
|
|
Severity::Low,
|
|
|
|
|
),
|
|
|
|
|
_ => {
|
|
|
|
|
// Check if it's a guard/auth call
|
2026-02-25 04:02:11 -05:00
|
|
|
if super::is_guard_call(info, ctx.lang, ctx.analysis_rules)
|
|
|
|
|
|| super::is_auth_call(info, ctx.lang)
|
|
|
|
|
{
|
2026-02-24 23:44:07 -05:00
|
|
|
(
|
|
|
|
|
"cfg-unreachable-guard",
|
|
|
|
|
"Unreachable guard/auth check",
|
|
|
|
|
Severity::Medium,
|
|
|
|
|
)
|
|
|
|
|
} else {
|
|
|
|
|
// Plain unreachable code — low severity
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let callee_desc = info.callee.as_deref().unwrap_or("(unknown)");
|
|
|
|
|
|
|
|
|
|
findings.push(CfgFinding {
|
|
|
|
|
rule_id: rule_id.to_string(),
|
|
|
|
|
title: title.to_string(),
|
|
|
|
|
severity,
|
|
|
|
|
confidence: Confidence::High,
|
|
|
|
|
span: info.span,
|
|
|
|
|
message: format!("{title}: `{callee_desc}` is unreachable and will never execute"),
|
|
|
|
|
evidence: vec![idx],
|
|
|
|
|
score: None,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
findings
|
|
|
|
|
}
|
|
|
|
|
}
|