nyx/src/cfg_analysis/unreachable.rs
Eli Peter 19b578c5c4
Feat/configurable sanitizers and js precision (#32)
* chore: Exclude CLAUDE.md from Cargo.toml

* feat: Add configurable analysis rules and CLI commands for custom sanitizers and terminators

* feat: Enhance resource management and analysis efficiency

- Implemented parallel summary merging in `scan_filesystem` using rayon for improved performance.
- Introduced `GlobalSummaries::merge()` for efficient merging of summaries.
- Optimized file reading and hashing to eliminate redundant I/O operations.
- Added `should_scan_with_hash()` and `upsert_file_with_hash()` methods to streamline file processing.
- Enhanced taint analysis with in-place mutations to reduce memory allocations.
- Updated resource acquisition patterns to exclude false positives for `freopen` and wrapper functions.

* feat: Implement severity downgrade for findings in non-production paths and add source kind inference

* feat: Update versioning information in SECURITY.md for new stable line

* feat: Update categories in Cargo.toml to include parser-implementations and text-processing

* feat: Update dependencies in Cargo.lock for improved compatibility and performance

* feat: Update dependencies in Cargo.lock and Cargo.toml for improved compatibility
2026-02-25 04:02:11 -05:00

116 lines
3.9 KiB
Rust

use super::dominators;
use super::{AnalysisContext, CfgAnalysis, CfgFinding, Confidence};
use crate::cfg::StmtKind;
use crate::labels::DataLabel;
use crate::patterns::Severity;
use std::collections::HashSet;
pub struct UnreachableCode;
/// 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
}
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);
let handler_callbacks = event_handler_callbacks(ctx);
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;
}
// Suppress findings for nodes inside event handler callbacks
if let Some(func_name) = &info.enclosing_func
&& handler_callbacks.contains(func_name)
{
continue;
}
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
if super::is_guard_call(info, ctx.lang, ctx.analysis_rules)
|| super::is_auth_call(info, ctx.lang)
{
(
"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
}
}