mirror of
https://github.com/elicpeter/nyx.git
synced 2026-06-09 19:45:13 +02:00
style(comments): remove decorative comment borders across files for consistency and cleaner code structure
This commit is contained in:
parent
321d0a61ab
commit
879f965379
39 changed files with 16 additions and 379 deletions
14
src/ast.rs
14
src/ast.rs
|
|
@ -1043,9 +1043,7 @@ fn downgrade_severity(s: Severity) -> Severity {
|
|||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// ParsedSource + ParsedFile: shared parse/CFG pipeline
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Level 1: parsed tree + lang info. No CFG construction.
|
||||
struct ParsedSource<'a> {
|
||||
|
|
@ -2161,9 +2159,7 @@ impl<'a> ParsedFile<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Pass 1: Extract function summaries (no taint analysis)
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Extract function summaries from pre-read bytes.
|
||||
///
|
||||
|
|
@ -2453,9 +2449,7 @@ pub fn extract_all_summaries_from_bytes(
|
|||
))
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Constant-argument suppression helper
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Returns `true` when the captured call node has only literal arguments
|
||||
/// (string, number, boolean, null/nil/none), or identifier arguments that
|
||||
|
|
@ -5355,9 +5349,7 @@ fn has_interpolation(node: tree_sitter::Node) -> bool {
|
|||
false
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Layer B: AST pattern suppression when taint confirms safety
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Map the second segment of a pattern ID (e.g. "cmdi" from "py.cmdi.os_system")
|
||||
/// to the `Cap` that taint analysis models. Returns `None` for categories taint
|
||||
|
|
@ -5738,9 +5730,7 @@ impl TaintSuppressionCtx {
|
|||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Pass 2 / single‑file: Full rule execution (AST queries + taint)
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Run all enabled analyses on pre-read bytes and return diagnostics.
|
||||
///
|
||||
|
|
@ -5816,9 +5806,7 @@ pub fn run_rules_on_file(
|
|||
run_rules_on_bytes(&bytes, path, cfg, global_summaries, scan_root)
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Fused single-pass: extract summaries + run full analysis in one parse/CFG
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Result of a fused analysis pass: both function summaries and diagnostics.
|
||||
pub struct FusedResult {
|
||||
|
|
@ -6090,9 +6078,7 @@ pub fn analyse_file_fused(
|
|||
})
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Text-based pattern scanning (non-tree-sitter files)
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Run text-based pattern scanners on files whose extension is not supported
|
||||
/// by tree-sitter. Currently handles `.ejs` templates.
|
||||
|
|
|
|||
|
|
@ -15,9 +15,7 @@ use serde::{Deserialize, Serialize};
|
|||
use std::collections::HashMap;
|
||||
use std::path::Path;
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Baseline entry (stripped — no source code)
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// A stripped baseline entry: only what is needed for cross-commit diffing.
|
||||
/// Contains no source code snippets.
|
||||
|
|
@ -33,9 +31,7 @@ pub struct BaselineEntry {
|
|||
pub rule_id: String,
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Transition enum
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// How a finding's verdict changed between the baseline scan and the current
|
||||
/// scan.
|
||||
|
|
@ -59,9 +55,7 @@ pub enum Transition {
|
|||
FlippedNotConfirmed,
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// VerdictDiffEntry
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Per-finding verdict diff produced by comparing a baseline to a current scan.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
|
|
@ -87,9 +81,7 @@ pub struct VerdictDiff {
|
|||
pub entries: Vec<VerdictDiffEntry>,
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Load / write helpers
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Load baseline entries from a file.
|
||||
///
|
||||
|
|
@ -164,9 +156,7 @@ pub fn write_baseline(path: &Path, diags: &[Diag]) -> crate::errors::NyxResult<(
|
|||
})
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Diff computation
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
fn classify_transition(
|
||||
baseline: Option<VerifyStatus>,
|
||||
|
|
@ -272,9 +262,7 @@ pub fn compute_verdict_diff(baseline: &[BaselineEntry], current: &[Diag]) -> Ver
|
|||
VerdictDiff { entries }
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// CI gates
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Gate: exit code 2 if any new `Confirmed` finding appears.
|
||||
///
|
||||
|
|
@ -320,9 +308,7 @@ pub fn check_gate(diff: &VerdictDiff, gate: &str) -> bool {
|
|||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Console / JSON rendering
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
fn status_str(s: Option<VerifyStatus>) -> &'static str {
|
||||
match s {
|
||||
|
|
@ -393,9 +379,7 @@ pub fn format_diff_console(diff: &VerdictDiff) -> String {
|
|||
lines.join("\n") + "\n"
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Tests
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
|
|
|||
|
|
@ -20,9 +20,7 @@ use smallvec::SmallVec;
|
|||
use std::collections::{BTreeMap, HashMap};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Types
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Metadata attached to each call-graph edge.
|
||||
#[derive(Debug, Clone)]
|
||||
|
|
@ -76,9 +74,7 @@ pub struct CallGraphAnalysis {
|
|||
pub topo_scc_callee_first: Vec<usize>,
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Callee-name normalization
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Extract the last segment of a qualified callee name for resolution.
|
||||
///
|
||||
|
|
@ -164,9 +160,7 @@ pub(crate) fn callee_container_hint(raw: &str) -> &str {
|
|||
""
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Class / container → method index
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Per-language `(container, method_name)` → candidate [`FuncKey`] index.
|
||||
///
|
||||
|
|
@ -374,9 +368,7 @@ impl TypeHierarchyIndex {
|
|||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Call-graph construction
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Build the whole-program call graph from merged summaries.
|
||||
///
|
||||
|
|
@ -742,9 +734,7 @@ fn resolve_via_interop(
|
|||
None
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// SCC / topological analysis
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Compute SCC decomposition and topological ordering of the call graph.
|
||||
///
|
||||
|
|
@ -772,9 +762,7 @@ pub fn analyse(cg: &CallGraph) -> CallGraphAnalysis {
|
|||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// File-level batch ordering
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// A batch of files at a single topological position, annotated with whether
|
||||
/// any contributing SCC contains mutual recursion (len > 1) and whether any
|
||||
|
|
@ -1139,9 +1127,7 @@ pub(super) fn scc_file_batches<'a>(
|
|||
(batches, orphans)
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Tests
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
|
|
|||
|
|
@ -121,9 +121,7 @@ fn extract_case_literal_text<'a>(case: Node<'a>, lang: &str, code: &'a [u8]) ->
|
|||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Exception-source detection for try/catch wiring
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// Returns true if this CFG node can implicitly raise an exception (calls).
|
||||
/// Explicit throws are collected separately via `throw_targets`.
|
||||
|
|
@ -190,9 +188,7 @@ pub(super) fn extract_catch_param_name<'a>(
|
|||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Ruby begin/rescue/ensure handler
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// Builds CFG for Ruby's `begin`/`rescue`/`ensure` blocks (and `body_statement`
|
||||
/// with inline rescue). Ruby's `begin` has no `body` field, the try-body
|
||||
|
|
@ -442,9 +438,7 @@ pub(super) fn build_begin_rescue<'a>(
|
|||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// switch handler, multi-way dispatch with fallthrough
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// True for AST kinds that wrap a single switch case body.
|
||||
pub(super) fn is_switch_case_kind(kind: &str) -> bool {
|
||||
|
|
@ -780,9 +774,7 @@ pub(super) fn build_switch<'a>(
|
|||
exits
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// try/catch/finally handler
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(super) fn build_try<'a>(
|
||||
|
|
|
|||
|
|
@ -388,9 +388,7 @@ fn js_catch_no_param_no_synthetic() {
|
|||
);
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
// Ruby begin/rescue/ensure tests
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn ruby_begin_rescue_has_exception_edges() {
|
||||
|
|
@ -540,9 +538,7 @@ fn ruby_multiple_rescue_clauses() {
|
|||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
// Short-circuit evaluation tests
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Helper: collect all If nodes from the CFG.
|
||||
fn if_nodes(cfg: &Cfg) -> Vec<NodeIndex> {
|
||||
|
|
@ -2008,10 +2004,8 @@ fn local_summary_callees_have_distinct_ordinals() {
|
|||
assert_ne!(ord0, ord1, "ordinals must differ across sites");
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
// Anonymous function body naming via syntactic context
|
||||
// (derive_anon_fn_name_from_context coverage)
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
fn js_body_names(src: &[u8]) -> Vec<String> {
|
||||
let ts_lang = Language::from(tree_sitter_javascript::LANGUAGE);
|
||||
|
|
@ -2531,9 +2525,7 @@ fn pointer_disabled_skips_subscript_synthesis() {
|
|||
});
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
// Gap-filling: switch / for / do-while / nested loops / re-throw
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
|
||||
/// JS `switch` should produce one synthetic dispatch `If` node per
|
||||
/// case (default excluded when at the tail), plus True edges into
|
||||
|
|
@ -2908,12 +2900,10 @@ fn js_empty_function_body_well_formed() {
|
|||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
// Loop CFG structure: every loop variant must produce a Loop header
|
||||
// with at least one Back edge that targets that header. Without these
|
||||
// invariants the SSA loop-induction-variable phi placement is wrong
|
||||
// and the abstract-interp widening points are missed.
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
fn loop_headers(cfg: &Cfg) -> Vec<NodeIndex> {
|
||||
cfg.node_indices()
|
||||
|
|
@ -3998,9 +3988,7 @@ function outer(obj, x, y) {
|
|||
assert_eq!(mline, 4, "obj.method(x) on line 4");
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
// Constant-branch fold: CondArith capture + evaluation
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
|
||||
/// `CondArith::eval`/`eval_bool` must fold the two OWASP-Benchmark
|
||||
/// arithmetic guard shapes to a definite boolean, using integer
|
||||
|
|
|
|||
|
|
@ -10,9 +10,7 @@ use petgraph::graph::NodeIndex;
|
|||
use smallvec::SmallVec;
|
||||
use tree_sitter::Node;
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Short-circuit boolean operator helpers
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub(super) enum BoolOp {
|
||||
|
|
|
|||
|
|
@ -90,9 +90,7 @@ fn collect_ts_type_alias_local_collections(root: Node<'_>, code: &[u8], out: &mu
|
|||
});
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
// Java
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Walk the AST for `class_declaration` nodes whose body contains
|
||||
/// `field_declaration`s with classifiable types. Only class-level
|
||||
|
|
@ -144,9 +142,7 @@ fn collect_java(root: Node<'_>, code: &[u8], out: &mut HashMap<String, DtoFields
|
|||
});
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
// TypeScript / JavaScript
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Walk for `interface_declaration` and `class_declaration` nodes.
|
||||
/// Interfaces with `property_signature` children and classes with
|
||||
|
|
@ -224,9 +220,7 @@ fn extract_ts_property<'a>(node: Node<'a>, code: &'a [u8]) -> Option<(String, Ty
|
|||
Some((field_name, kind))
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
// Rust
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Walk for `struct_item` nodes whose body lists named fields.
|
||||
fn collect_rust(root: Node<'_>, code: &[u8], out: &mut HashMap<String, DtoFields>) {
|
||||
|
|
@ -276,9 +270,7 @@ fn collect_rust(root: Node<'_>, code: &[u8], out: &mut HashMap<String, DtoFields
|
|||
});
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
// Python (Pydantic)
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Walk for `class_definition` nodes whose superclass list contains
|
||||
/// `BaseModel` / `pydantic.BaseModel`. Each `expression_statement` in
|
||||
|
|
@ -360,9 +352,7 @@ fn python_inherits_basemodel<'a>(class_node: Node<'a>, code: &'a [u8]) -> bool {
|
|||
false
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
// Walk helper
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
fn walk<'a, F: FnMut(Node<'a>)>(node: Node<'a>, f: &mut F) {
|
||||
f(node);
|
||||
|
|
|
|||
|
|
@ -4,9 +4,7 @@ use crate::labels::{DataLabel, Kind, classify, lookup};
|
|||
use smallvec::SmallVec;
|
||||
use tree_sitter::Node;
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Utility helpers
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// Return the text of a node.
|
||||
#[inline]
|
||||
|
|
|
|||
|
|
@ -54,9 +54,7 @@ pub(crate) fn collect_hierarchy_edges(
|
|||
acc
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
// Java
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
fn collect_java<F: FnMut(String, String)>(root: Node<'_>, code: &[u8], push: &mut F) {
|
||||
walk(root, &mut |node| {
|
||||
|
|
@ -146,9 +144,7 @@ fn type_identifier_text(n: Node<'_>, code: &[u8]) -> Option<String> {
|
|||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
// Rust
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Walk for `impl_item` nodes and emit edges from the concrete type to
|
||||
/// the trait being implemented. Inherent impls (`impl Foo {}`) emit
|
||||
|
|
@ -199,9 +195,7 @@ fn rust_path_leaf(n: Node<'_>, code: &[u8]) -> Option<String> {
|
|||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
// TypeScript / JavaScript
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
fn collect_ts<F: FnMut(String, String)>(root: Node<'_>, code: &[u8], push: &mut F) {
|
||||
walk(root, &mut |node| {
|
||||
|
|
@ -268,9 +262,7 @@ fn collect_ts_heritage<F: FnMut(String, String)>(
|
|||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
// Python
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
fn collect_python<F: FnMut(String, String)>(root: Node<'_>, code: &[u8], push: &mut F) {
|
||||
walk(root, &mut |node| {
|
||||
|
|
@ -314,9 +306,7 @@ fn python_base_text(n: Node<'_>, code: &[u8]) -> Option<String> {
|
|||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
// Ruby
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
fn collect_ruby<F: FnMut(String, String)>(root: Node<'_>, code: &[u8], push: &mut F) {
|
||||
walk(root, &mut |node| {
|
||||
|
|
@ -345,9 +335,7 @@ fn collect_ruby<F: FnMut(String, String)>(root: Node<'_>, code: &[u8], push: &mu
|
|||
});
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
// PHP
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
fn collect_php<F: FnMut(String, String)>(root: Node<'_>, code: &[u8], push: &mut F) {
|
||||
walk(root, &mut |node| {
|
||||
|
|
@ -382,9 +370,7 @@ fn collect_php<F: FnMut(String, String)>(root: Node<'_>, code: &[u8], push: &mut
|
|||
});
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
// C++
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
fn collect_cpp<F: FnMut(String, String)>(root: Node<'_>, code: &[u8], push: &mut F) {
|
||||
walk(root, &mut |node| {
|
||||
|
|
@ -419,9 +405,7 @@ fn collect_cpp<F: FnMut(String, String)>(root: Node<'_>, code: &[u8], push: &mut
|
|||
});
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
// Helpers
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
fn walk<'a, F: FnMut(Node<'a>)>(node: Node<'a>, f: &mut F) {
|
||||
f(node);
|
||||
|
|
|
|||
|
|
@ -135,9 +135,7 @@ fn map_fs_module_to_promises(module: &str) -> Option<String> {
|
|||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Import binding extraction
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// Walk the top-level AST nodes and collect import alias bindings:
|
||||
///
|
||||
|
|
@ -615,6 +613,4 @@ fn scoped_identifier_matches(node: Node, code: &[u8], crate_prefix: &str, leaf:
|
|||
(Some(p), Some(l)) if p == crate_prefix && l == leaf)
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// === PUBLIC ENTRY POINT =================================================
|
||||
// -------------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -955,9 +955,7 @@ pub struct LocalFuncSummary {
|
|||
pub type Cfg = Graph<NodeInfo, EdgeKind>;
|
||||
pub type FuncSummaries = HashMap<FuncKey, LocalFuncSummary>;
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Per-body CFG types
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// Opaque identifier for an executable body within a file.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
|
|
@ -5075,10 +5073,8 @@ fn apply_arg_source_bindings(
|
|||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// The recursive *work‑horse* that converts an AST node into a CFG slice.
|
||||
// Returns the set of *exit* nodes that need to be wired further.
|
||||
// -------------------------------------------------------------------------
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(super) fn build_sub<'a>(
|
||||
ast: Node<'a>,
|
||||
|
|
@ -5099,9 +5095,7 @@ pub(super) fn build_sub<'a>(
|
|||
current_body_id: BodyId,
|
||||
) -> Vec<NodeIndex> {
|
||||
match lookup(lang, ast.kind()) {
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
// IF‑/ELSE: two branches that re‑merge afterwards
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
Kind::If => {
|
||||
// Some grammars (Go `if init; cond {}`, sibling C-style forms)
|
||||
// attach an init / "initializer" subtree that runs before the
|
||||
|
|
@ -5383,9 +5377,7 @@ pub(super) fn build_sub<'a>(
|
|||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
// WHILE / FOR: classic loop with a back edge.
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
Kind::While | Kind::For => {
|
||||
let header = push_node(
|
||||
g,
|
||||
|
|
@ -5527,9 +5519,7 @@ pub(super) fn build_sub<'a>(
|
|||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
// Control-flow sinks (return / break / continue).
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
Kind::Return => {
|
||||
if has_call_descendant(ast, lang) {
|
||||
// Return-call bug fix: emit a Call node BEFORE the Return so
|
||||
|
|
@ -5825,9 +5815,7 @@ pub(super) fn build_sub<'a>(
|
|||
current_body_id,
|
||||
),
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
// BLOCK: statements execute sequentially
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
Kind::SourceFile | Kind::Block => {
|
||||
// Ruby body_statement with rescue/ensure = implicit begin/rescue
|
||||
if lang == "ruby" && ast.kind() == "body_statement" {
|
||||
|
|
@ -6655,9 +6643,7 @@ pub(super) fn build_sub<'a>(
|
|||
analysis_rules,
|
||||
),
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
// Every other node = simple sequential statement
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
_ => {
|
||||
// React JSX `dangerouslySetInnerHTML={{__html: x}}` synthesis
|
||||
// (Phase 06): handles arrow-bodied components like
|
||||
|
|
@ -7145,21 +7131,5 @@ pub(crate) fn export_summaries(
|
|||
.collect()
|
||||
}
|
||||
|
||||
// pub(crate) fn dump_cfg(g: &Cfg) {
|
||||
// debug!(target: "taint", "CFG DUMP: nodes = {}, edges = {}", g.node_count(), g.edge_count());
|
||||
// for idx in g.node_indices() {
|
||||
// debug!(target: "taint", " node {:>3}: {:?}", idx.index(), g[idx]);
|
||||
// }
|
||||
// for e in g.edge_references() {
|
||||
// debug!(
|
||||
// target: "taint",
|
||||
// " edge {:>3} → {:<3} ({:?})",
|
||||
// e.source().index(),
|
||||
// e.target().index(),
|
||||
// e.weight()
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
|
||||
#[cfg(test)]
|
||||
mod cfg_tests;
|
||||
|
|
|
|||
|
|
@ -924,9 +924,7 @@ pub fn handle(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
// Shared post-processing helpers
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
/// Assign confidence, rank, and truncate diagnostics.
|
||||
pub(crate) fn post_process_diags(diags: &mut Vec<Diag>, cfg: &Config) {
|
||||
|
|
@ -1978,9 +1976,7 @@ fn run_topo_batches(
|
|||
result
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
// Two-pass scanning (no index)
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
/// Walk the filesystem and perform a two-pass scan:
|
||||
///
|
||||
|
|
@ -2487,9 +2483,7 @@ pub(crate) fn scan_filesystem_with_observer(
|
|||
Ok((diags, surface_map))
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
// Two-pass scanning (with index)
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
/// Indexed two-pass scan:
|
||||
///
|
||||
|
|
@ -3352,9 +3346,7 @@ pub fn scan_with_index_parallel_observer(
|
|||
Ok(diags)
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Low-noise prioritization pipeline
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Rules eligible for rollup grouping (high-frequency, low-signal patterns).
|
||||
const ROLLUP_RULES: &[&str] = &[
|
||||
|
|
@ -3610,9 +3602,7 @@ fn apply_low_budgets(
|
|||
stats.low_budget_dropped = before - diags.len();
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Inline suppression application
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Apply inline `nyx:ignore` / `nyx:ignore-next-line` suppressions to `diags`.
|
||||
///
|
||||
|
|
@ -3646,9 +3636,7 @@ fn apply_suppressions(diags: &mut [Diag]) {
|
|||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// dynamic verification summary tests
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
#[cfg(test)]
|
||||
mod dynamic_summary_tests {
|
||||
|
|
@ -3698,9 +3686,7 @@ mod dynamic_summary_tests {
|
|||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// deduplicate_taint_flows tests
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
#[cfg(test)]
|
||||
mod dedup_taint_flow_tests {
|
||||
|
|
@ -4268,9 +4254,7 @@ fn severity_filter_applied_at_output_stage() {
|
|||
assert_eq!(filtered[0].path, "src/main.rs");
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Prioritization pipeline tests
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
#[cfg(test)]
|
||||
mod prioritize_tests {
|
||||
|
|
|
|||
|
|
@ -207,9 +207,7 @@ fn collect_files(root: &Path, config: &Config) -> NyxResult<Vec<PathBuf>> {
|
|||
Ok(out)
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Text rendering
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Produce a human-readable tree. Files appear as top-level headers;
|
||||
/// each entry-point sits under its host file with its reach summary
|
||||
|
|
@ -434,9 +432,7 @@ fn es_kind_str(k: ExternalServiceKind) -> &'static str {
|
|||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// DOT / SVG rendering
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
pub fn render_dot(map: &SurfaceMap) -> String {
|
||||
let mut out = String::new();
|
||||
|
|
|
|||
|
|
@ -2166,9 +2166,7 @@ pub mod index {
|
|||
.collect::<Result<_, _>>()?)
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Scan persistence
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// Insert a new scan record.
|
||||
pub fn insert_scan(&self, record: &ScanRecord) -> NyxResult<()> {
|
||||
|
|
@ -2434,9 +2432,7 @@ pub mod index {
|
|||
Ok(rows)
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Triage state management
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// Get the triage state for a single finding fingerprint.
|
||||
/// Returns (state, note, updated_at) or None if no triage state exists.
|
||||
|
|
@ -2816,9 +2812,7 @@ pub mod index {
|
|||
Ok(count > 0)
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Maintenance utilities
|
||||
// -------------------------------------------------------------------------
|
||||
pub fn clear(&self) -> NyxResult<()> {
|
||||
self.c().execute_batch(
|
||||
r#"
|
||||
|
|
@ -2843,9 +2837,7 @@ pub mod index {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Helpers
|
||||
// -------------------------------------------------------------------------
|
||||
#[cfg(test)]
|
||||
fn digest_file(path: &Path) -> NyxResult<Vec<u8>> {
|
||||
let mut hasher = blake3::Hasher::new();
|
||||
|
|
|
|||
|
|
@ -83,6 +83,14 @@ pub fn syscall_number(name: &str) -> Option<u32> {
|
|||
"getgid" => 104,
|
||||
"geteuid" => 107,
|
||||
"getegid" => 108,
|
||||
"setuid" => 105,
|
||||
"setgid" => 106,
|
||||
"setreuid" => 113,
|
||||
"setregid" => 114,
|
||||
"setresuid" => 117,
|
||||
"setresgid" => 119,
|
||||
"setfsuid" => 122,
|
||||
"setfsgid" => 123,
|
||||
"sigaltstack" => 131,
|
||||
"setrlimit" => 160,
|
||||
"arch_prctl" => 158,
|
||||
|
|
@ -245,6 +253,14 @@ pub fn syscall_number(name: &str) -> Option<u32> {
|
|||
"geteuid" => 175,
|
||||
"getgid" => 176,
|
||||
"getegid" => 177,
|
||||
"setregid" => 143,
|
||||
"setgid" => 144,
|
||||
"setreuid" => 145,
|
||||
"setuid" => 146,
|
||||
"setresuid" => 147,
|
||||
"setresgid" => 149,
|
||||
"setfsuid" => 151,
|
||||
"setfsgid" => 152,
|
||||
"socket" => 198,
|
||||
"socketpair" => 199,
|
||||
"bind" => 200,
|
||||
|
|
|
|||
|
|
@ -191,9 +191,7 @@ pub fn detect_entries_in_file(
|
|||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
// JS / TS — Next.js (Phase 10) + Express (Phase 16)
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
fn detect_js_ts(root: Node<'_>, bytes: &[u8], path: &Path) -> HashMap<(usize, usize), EntryKind> {
|
||||
let mut entries: HashMap<(usize, usize), EntryKind> = HashMap::new();
|
||||
|
|
@ -727,9 +725,7 @@ fn express_receiver_text_matches(object: Node, bytes: &[u8]) -> bool {
|
|||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
// Python — Django / FastAPI / Flask
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
fn detect_python(root: Node, bytes: &[u8]) -> HashMap<(usize, usize), EntryKind> {
|
||||
let mut entries: HashMap<(usize, usize), EntryKind> = HashMap::new();
|
||||
|
|
@ -895,9 +891,7 @@ fn enclosing_python_class<'a>(node: Node<'a>) -> Option<Node<'a>> {
|
|||
None
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
// Java — Spring + JAX-RS
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
fn detect_java(root: Node, bytes: &[u8]) -> HashMap<(usize, usize), EntryKind> {
|
||||
let mut entries: HashMap<(usize, usize), EntryKind> = HashMap::new();
|
||||
|
|
@ -1016,9 +1010,7 @@ fn http_method_from_request_method_text(node: Node, bytes: &[u8]) -> Option<Http
|
|||
None
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
// Ruby — Rails + Sinatra
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
fn detect_ruby(root: Node, bytes: &[u8]) -> HashMap<(usize, usize), EntryKind> {
|
||||
let mut entries: HashMap<(usize, usize), EntryKind> = HashMap::new();
|
||||
|
|
@ -1108,9 +1100,7 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
// Rust — axum / actix-web / rocket
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
fn detect_rust(root: Node, bytes: &[u8]) -> HashMap<(usize, usize), EntryKind> {
|
||||
let mut entries: HashMap<(usize, usize), EntryKind> = HashMap::new();
|
||||
|
|
@ -1252,9 +1242,7 @@ fn rust_signature_has_axum_extractor(func: Node, bytes: &[u8]) -> bool {
|
|||
needles.iter().any(|n| text.contains(n))
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
// Go — net/http + gin / echo / chi
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
fn detect_go(root: Node, bytes: &[u8]) -> HashMap<(usize, usize), EntryKind> {
|
||||
let mut entries: HashMap<(usize, usize), EntryKind> = HashMap::new();
|
||||
|
|
@ -1305,9 +1293,7 @@ fn go_function_entry_kind(func: Node, bytes: &[u8]) -> Option<EntryKind> {
|
|||
None
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
// Tests
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
|
|
|||
|
|
@ -14,9 +14,7 @@ use serde::{Deserialize, Serialize};
|
|||
use std::fmt;
|
||||
use std::str::FromStr;
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Confidence
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Confidence level for a diagnostic finding.
|
||||
///
|
||||
|
|
@ -54,9 +52,7 @@ impl FromStr for Confidence {
|
|||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Flow Steps
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// The kind of operation at a flow step.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
|
|
@ -116,9 +112,7 @@ pub struct FlowStep {
|
|||
pub is_cross_file: bool,
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Symbolic verdict
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Symbolic verification verdict for a taint path.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
|
|
@ -156,9 +150,7 @@ pub struct SymbolicVerdict {
|
|||
pub cutoff_notes: Vec<String>,
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Dynamic verification verdict types (always present; not feature-gated)
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Why dynamic verification cannot be attempted for a finding.
|
||||
///
|
||||
|
|
@ -960,9 +952,7 @@ pub struct VerifyResult {
|
|||
pub hardening_outcome: Option<HardeningSummary>,
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Evidence
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Structured evidence for a diagnostic finding.
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||
|
|
@ -1108,9 +1098,7 @@ pub struct StateEvidence {
|
|||
pub to_state: String,
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// compute_confidence
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Derive a confidence level for `diag` based on its rule ID, severity,
|
||||
/// evidence, and analysis kind.
|
||||
|
|
@ -1422,9 +1410,7 @@ fn cap_specificity_score(notes: &[String]) -> i32 {
|
|||
0
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Explanation & Confidence Limiters
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Generate a human-readable explanation of a taint finding from its evidence.
|
||||
pub fn generate_explanation(diag: &Diag) -> Option<String> {
|
||||
|
|
@ -1592,9 +1578,7 @@ pub fn compute_confidence_limiters(diag: &Diag) -> Vec<String> {
|
|||
limiters
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Tests
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
|
|
|||
10
src/fmt.rs
10
src/fmt.rs
|
|
@ -13,9 +13,7 @@ use std::collections::BTreeMap;
|
|||
/// Default maximum line width when terminal size is unknown.
|
||||
const DEFAULT_WIDTH: usize = 100;
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Public API
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Render all diagnostics as grouped, formatted console output with a summary.
|
||||
///
|
||||
|
|
@ -190,9 +188,7 @@ pub fn shorten_callee(s: &str) -> String {
|
|||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Welcome screen
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Render the branded welcome screen shown when `nyx` is invoked with no arguments.
|
||||
pub fn render_welcome() -> String {
|
||||
|
|
@ -258,9 +254,7 @@ const LOGO: &[&str] = &[
|
|||
r"╚═╝ ╚═══╝ ╚═╝ ╚═╝ ╚═╝",
|
||||
];
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Internal rendering
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Indentation for body/evidence lines (spaces).
|
||||
const BODY_INDENT: usize = 6;
|
||||
|
|
@ -670,9 +664,7 @@ fn severity_tag(sev: Severity) -> String {
|
|||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Text utilities
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Collapse spacing artefacts in method chains.
|
||||
///
|
||||
|
|
@ -775,9 +767,7 @@ fn capitalize_first(s: &str) -> String {
|
|||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Tests
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
|
|
|||
10
src/main.rs
10
src/main.rs
|
|
@ -11,13 +11,8 @@ use std::time::Instant;
|
|||
use tracing_subscriber::fmt::time;
|
||||
use tracing_subscriber::prelude::*;
|
||||
use tracing_subscriber::{EnvFilter, Registry, fmt as tracing_fmt};
|
||||
// use tracing_appender::rolling::{RollingFileAppender, Rotation};
|
||||
// use tracing_appender::non_blocking;
|
||||
|
||||
fn init_tracing(quiet: bool) {
|
||||
// let file_appender = RollingFileAppender::new(Rotation::HOURLY, "logs", "nyx-scanner.log");
|
||||
// let (file_writer, guard) = non_blocking(file_appender);
|
||||
|
||||
let filter = if quiet {
|
||||
EnvFilter::new("off")
|
||||
} else {
|
||||
|
|
@ -30,11 +25,6 @@ fn init_tracing(quiet: bool) {
|
|||
.with_thread_ids(true)
|
||||
.with_timer(time::UtcTime::rfc_3339());
|
||||
|
||||
// let file_layer = fmt::layer()
|
||||
// .with_writer(file_writer)
|
||||
// .without_time()
|
||||
// .json();
|
||||
|
||||
Registry::default().with(filter).with(fmt_layer).init();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -234,9 +234,7 @@ pub fn rank_diags(diags: &mut [Diag]) {
|
|||
});
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Scoring helpers
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Rank delta from the dynamic verification verdict.
|
||||
///
|
||||
|
|
@ -394,9 +392,7 @@ fn state_finding_bonus(rule_id: &str) -> f64 {
|
|||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Tests
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
|
|
|||
|
|
@ -52,9 +52,7 @@ impl RustUseMap {
|
|||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Module path derivation
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Find the crate root by walking up from `file_path` looking for `Cargo.toml`.
|
||||
///
|
||||
|
|
@ -137,9 +135,7 @@ pub fn derive_module_path(file_path: &Path, scan_root: Option<&Path>) -> Option<
|
|||
Some(path)
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Use-declaration parsing
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Parse every top-level `use_declaration` of a Rust source tree into a
|
||||
/// [`RustUseMap`].
|
||||
|
|
@ -328,9 +324,7 @@ fn join_segments(prefix: &[String], suffix: &[String]) -> String {
|
|||
all.join("::")
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Resolution helpers
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Resolve a Rust callee `(qualifier, name)` against a use map.
|
||||
///
|
||||
|
|
@ -389,9 +383,7 @@ pub fn split_module_and_name(qualified: &str) -> (String, String) {
|
|||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Tests
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
|
|
|||
|
|
@ -33,9 +33,7 @@ use serde::Serialize;
|
|||
use std::collections::VecDeque;
|
||||
use std::path::Path;
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Line-number helper
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Convert a byte offset to a 1-based line number.
|
||||
fn byte_offset_to_line(bytes: &[u8], offset: usize) -> usize {
|
||||
|
|
@ -43,9 +41,7 @@ fn byte_offset_to_line(bytes: &[u8], offset: usize) -> usize {
|
|||
bytes[..offset].iter().filter(|&&b| b == b'\n').count() + 1
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Cap → human-readable names
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
fn cap_names(c: Cap) -> Vec<String> {
|
||||
let mut names = Vec::new();
|
||||
|
|
@ -96,9 +92,7 @@ fn label_str(l: &DataLabel) -> String {
|
|||
}
|
||||
}
|
||||
|
||||
// ═════════════════════════════════════════════════════════════════════════════
|
||||
// View-model types
|
||||
// ═════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
// ── Function list ────────────────────────────────────────────────────────────
|
||||
|
||||
|
|
@ -1397,9 +1391,7 @@ fn route_view(r: &RouteRegistration, _bytes: &[u8]) -> AuthRouteView {
|
|||
}
|
||||
}
|
||||
|
||||
// ═════════════════════════════════════════════════════════════════════════════
|
||||
// On-demand analysis pipeline
|
||||
// ═════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
/// Result of parsing + CFG construction for a single file.
|
||||
pub struct FileAnalysis {
|
||||
|
|
|
|||
|
|
@ -315,11 +315,9 @@ mod tests {
|
|||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
// Skip-conditions: copy-prop must NOT erase semantic info attached
|
||||
// to a copy's CFG node. These guard the three early-exits in
|
||||
// `copy_propagate`: labels, numeric-length, and string_prefix.
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Build a single-block SSA body containing
|
||||
/// v0 = Const, v1 = Assign(v0)
|
||||
|
|
|
|||
|
|
@ -2227,9 +2227,7 @@ fn rename_variables(
|
|||
)
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Debug invariant checkers
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Verify BFS block ordering: every non-entry, non-orphan block must have at
|
||||
/// least one predecessor with a smaller block ID.
|
||||
|
|
@ -3532,9 +3530,7 @@ mod tests {
|
|||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
// FieldProj chain lowering tests
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
//
|
||||
// These tests pin the contract that `try_lower_field_proj_chain`
|
||||
// emits a `FieldProj` chain for chained-receiver method calls
|
||||
|
|
@ -4370,11 +4366,9 @@ mod tests {
|
|||
);
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
// SSA edge cases: loop induction, multi-variable phis, multiple
|
||||
// returns, switch-cases, and shadowing. These plug holes in the
|
||||
// dominator-frontier / variable-renaming coverage.
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Loop induction variable: `x = x + 1` inside a loop is the
|
||||
/// canonical SSA challenge, the body uses `x` then redefines it,
|
||||
|
|
|
|||
|
|
@ -479,11 +479,9 @@ mod tests {
|
|||
let n1 = NodeIndex::new(1);
|
||||
let n2 = NodeIndex::new(2);
|
||||
|
||||
// Push n0
|
||||
assert!(in_wl.insert(n0));
|
||||
wl.push_back(n0);
|
||||
|
||||
// Push n1
|
||||
assert!(in_wl.insert(n1));
|
||||
wl.push_back(n1);
|
||||
|
||||
|
|
@ -492,7 +490,6 @@ mod tests {
|
|||
// wl still has only 2 entries
|
||||
assert_eq!(wl.len(), 2);
|
||||
|
||||
// Pop n0
|
||||
let popped = wl.pop_front().unwrap();
|
||||
in_wl.remove(&popped);
|
||||
assert_eq!(popped, n0);
|
||||
|
|
@ -503,7 +500,6 @@ mod tests {
|
|||
assert!(in_wl.insert(n0));
|
||||
wl.push_back(n0);
|
||||
|
||||
// Push n2
|
||||
assert!(in_wl.insert(n2));
|
||||
wl.push_back(n2);
|
||||
|
||||
|
|
|
|||
|
|
@ -1558,9 +1558,7 @@ mod tests {
|
|||
));
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
// chain-receiver decomposition + chain_proxies tracking
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
//
|
||||
// These tests pin the contract that:
|
||||
// 1. `try_chain_decompose` parses dotted callees into receiver +
|
||||
|
|
@ -1981,12 +1979,10 @@ mod tests {
|
|||
assert!(lc.contains(ResourceLifecycle::CLOSED));
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
// Pointer-analysis: PtrProxyHint::FieldOnly routes
|
||||
// single-dot proxy-acquire to chain_proxies, suppressing the
|
||||
// SymbolId path that would otherwise mark the field-aliased local
|
||||
// as a leakable resource.
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn field_only_hint_routes_single_dot_acquire_to_chain_proxies() {
|
||||
|
|
|
|||
|
|
@ -9,9 +9,7 @@
|
|||
|
||||
use std::collections::HashMap;
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Public types
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Whether the directive suppresses on its own line or the next line.
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
|
|
@ -30,9 +28,7 @@ pub struct SuppressionMeta {
|
|||
pub directive_line: usize,
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Internal types
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// A single rule matcher, either exact or wildcard-suffix (`foo.*`).
|
||||
#[derive(Debug)]
|
||||
|
|
@ -99,9 +95,7 @@ impl SuppressionIndex {
|
|||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Canonical rule ID
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Strip parenthetical suffix from a rule ID:
|
||||
/// `"taint-unsanitised-flow (source 5:1)"` → `"taint-unsanitised-flow"`.
|
||||
|
|
@ -114,9 +108,7 @@ pub fn canonical_rule_id(id: &str) -> &str {
|
|||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Comment style per language
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
enum CommentStyle {
|
||||
|
|
@ -152,9 +144,7 @@ fn comment_style_for_path(path: &std::path::Path) -> Option<CommentStyle> {
|
|||
comment_style_for_ext(norm)
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Parser
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Parse inline suppression directives from `source`, using comment syntax
|
||||
/// appropriate for the given file path.
|
||||
|
|
@ -479,9 +469,7 @@ fn parse_rule_ids(text: &str) -> Vec<RuleMatcher> {
|
|||
.collect()
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Tests
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
|
|
|||
|
|
@ -33,9 +33,7 @@ use super::state::{PathConstraint, SymbolicState};
|
|||
use super::transfer::{self, SymexHeapCtx, SymexSummaryCtx};
|
||||
use super::value::SymbolicValue;
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Budget constants
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Maximum branch forks per finding before falling back to single-path.
|
||||
const MAX_FORKS_PER_FINDING: usize = 3;
|
||||
|
|
@ -47,9 +45,7 @@ const MAX_PATHS_PER_FINDING: usize = 8;
|
|||
/// ALL paths for one finding. Global, not per-path.
|
||||
const MAX_TOTAL_STEPS: usize = 500;
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Types
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// A single exploration path in flight.
|
||||
///
|
||||
|
|
@ -103,9 +99,7 @@ pub(super) struct ExplorationResult {
|
|||
pub interproc_cutoffs: Vec<super::interproc::CutoffReason>,
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Reachability
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Compute the set of blocks on some CFG path from source to sink.
|
||||
///
|
||||
|
|
@ -172,9 +166,7 @@ fn compute_source_sink_reachable(
|
|||
forward.intersection(&backward).copied().collect()
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Exploration engine
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Run multi-path symbolic exploration for a single finding.
|
||||
///
|
||||
|
|
@ -1138,9 +1130,7 @@ fn try_extract_witness(
|
|||
.or_else(|| state.sym_state.get_sink_witness(finding, ssa))
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Verdict aggregation
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
impl ExplorationResult {
|
||||
/// Aggregate per-path outcomes into a single [`SymbolicVerdict`].
|
||||
|
|
@ -1221,9 +1211,7 @@ impl ExplorationResult {
|
|||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Witness enrichment
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Append interprocedural context to a witness string.
|
||||
///
|
||||
|
|
@ -1269,9 +1257,7 @@ fn append_interproc_context(
|
|||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Tests
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
|
|
|||
|
|
@ -35,9 +35,7 @@ const MAX_FIELDS_PER_OBJECT: usize = 8;
|
|||
/// `Elements` (taint unioned, value set to `Unknown`).
|
||||
pub const MAX_TRACKED_INDICES: usize = 16;
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Types
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Heap key: allocation-site identity + field slot.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
|
|
@ -365,9 +363,7 @@ impl SymbolicHeap {
|
|||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Helpers
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Resolve a container operation index argument to a [`FieldSlot`].
|
||||
///
|
||||
|
|
@ -440,9 +436,7 @@ pub fn resolve_singleton_object(
|
|||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Tests
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
|
|
|||
|
|
@ -45,9 +45,7 @@ use super::state::{PathConstraint, SymbolicState};
|
|||
use super::transfer::{self, SymexHeapCtx, SymexSummaryCtx};
|
||||
use super::value::{SymbolicValue, mk_phi};
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Constants
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Default max call depth (caller → callee → callee's callee → ...).
|
||||
pub(crate) const DEFAULT_MAX_DEPTH: usize = 3;
|
||||
|
|
@ -91,9 +89,7 @@ pub(crate) const DEFAULT_MAX_SCC_REENTRY: usize = 3;
|
|||
/// Max cache entries before eviction (simple clear).
|
||||
const MAX_CACHE_ENTRIES: usize = 64;
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Feature gate
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Check if interprocedural symbolic execution is enabled.
|
||||
///
|
||||
|
|
@ -106,9 +102,7 @@ pub fn interproc_enabled() -> bool {
|
|||
.interprocedural
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Cutoff reasons
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Structured record of why interprocedural execution was cut short.
|
||||
///
|
||||
|
|
@ -234,9 +228,7 @@ impl fmt::Display for CutoffReason {
|
|||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Context
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Shared context for interprocedural symbolic execution.
|
||||
///
|
||||
|
|
@ -354,9 +346,7 @@ pub struct InterprocStats {
|
|||
pub forks: usize,
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Result types
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Result of executing a callee to completion.
|
||||
#[derive(Clone, Debug)]
|
||||
|
|
@ -441,9 +431,7 @@ pub struct InterprocEvents {
|
|||
pub cutoff_reasons: Vec<CutoffReason>,
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Merge policy
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Policy for merging multiple callee exit states into a single caller state.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
|
|
@ -467,9 +455,7 @@ pub fn select_merge_policy(exit_count: usize, has_cutoffs: bool) -> MergePolicy
|
|||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Cache
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Cache key abstraction of argument symbolic values.
|
||||
///
|
||||
|
|
@ -520,9 +506,7 @@ impl ArgAbstraction {
|
|||
/// Cache type: maps (callee_name, arg_abstraction, heap_fingerprint) → CallOutcome.
|
||||
pub type InterprocCache = HashMap<(String, ArgAbstraction, u64), CallOutcome>;
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// RAII re-entry guard
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// RAII guard that increments a function's re-entry count on creation and
|
||||
/// decrements it on drop. Ensures the count is correct on all exit paths.
|
||||
|
|
@ -550,9 +534,7 @@ impl<'a> Drop for ReentryGuard<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Core execution
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Execute a callee's SSA body interprocedurally.
|
||||
///
|
||||
|
|
@ -1151,9 +1133,7 @@ fn handle_nested_calls(
|
|||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Exit state merging
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Merge multiple callee exit states into a single state for the caller.
|
||||
///
|
||||
|
|
@ -1256,9 +1236,7 @@ fn merge_most_tainted(states: &[CalleeExitState]) -> CalleeExitState {
|
|||
})
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Heap delta
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Compute the set of heap fields that changed between initial and final state.
|
||||
fn compute_heap_delta(initial: &SymbolicHeap, final_heap: &SymbolicHeap) -> Vec<HeapMutation> {
|
||||
|
|
@ -1293,9 +1271,7 @@ fn sym_value_structurally_eq(a: &SymbolicValue, b: &SymbolicValue) -> bool {
|
|||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Tests
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
|
|
|||
|
|
@ -34,9 +34,7 @@ pub struct LoopInfo {
|
|||
doms: Dominators<NodeIndex>,
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Public API
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Analyse loop structure in an SSA body.
|
||||
///
|
||||
|
|
@ -108,9 +106,7 @@ impl LoopInfo {
|
|||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Internal helpers
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Build a petgraph from SSA block successors.
|
||||
///
|
||||
|
|
@ -304,9 +300,7 @@ fn is_simple_increment(ssa: &SsaBody, inc_val: SsaValue, phi_val: SsaValue) -> b
|
|||
false
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Tests
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
|
|
|||
|
|
@ -46,9 +46,7 @@ use crate::ssa::type_facts::TypeKind;
|
|||
|
||||
use super::state::{PathConstraint, SymbolicState};
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Constants
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Maximum SMT queries per finding (across all paths).
|
||||
const MAX_SMT_QUERIES_PER_FINDING: u32 = 10;
|
||||
|
|
@ -60,9 +58,7 @@ const SMT_QUERY_TIMEOUT_MS: u32 = 500;
|
|||
/// String theory (especially lexicographic ordering) is more expensive.
|
||||
const SMT_STRING_QUERY_TIMEOUT_MS: u32 = 500;
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Types
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Result of an SMT satisfiability check.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
|
|
@ -126,9 +122,7 @@ fn warm_z3() {
|
|||
});
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// SmtContext
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
impl SmtContext {
|
||||
/// Create a new SMT context for one finding's exploration.
|
||||
|
|
@ -208,9 +202,7 @@ impl SmtContext {
|
|||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Sort inference
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Try to determine that an SSA value is an integer from PathEnv facts.
|
||||
fn is_known_int(v: SsaValue, env: &PathEnv) -> bool {
|
||||
|
|
@ -303,9 +295,7 @@ fn force_str_var(var_map: &mut VarMap, v: SsaValue) -> Option<Z3Str> {
|
|||
Some(z3_var)
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// PathEnv seeding
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Seed Z3 solver with known facts from PathEnv.
|
||||
///
|
||||
|
|
@ -411,9 +401,7 @@ fn seed_from_path_env(solver: &Solver, var_map: &mut VarMap, env: &PathEnv) {
|
|||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Constraint translation
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Translate a single path constraint into a Z3 assertion.
|
||||
///
|
||||
|
|
@ -583,9 +571,7 @@ fn build_comparison_str(lhs: &Z3Str, op: CompOp, rhs: &Z3Str) -> z3::ast::Bool {
|
|||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Escalation predicate
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Determine whether accumulated path constraints warrant SMT escalation.
|
||||
///
|
||||
|
|
@ -613,9 +599,7 @@ fn can_translate_operand(op: &Operand) -> bool {
|
|||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Tests
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
|
|
|||
|
|
@ -213,9 +213,7 @@ impl Default for SymbolicState {
|
|||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Tests
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
|
|
|||
|
|
@ -13,9 +13,7 @@ use crate::symbol::Lang;
|
|||
|
||||
use super::value::SymbolicValue;
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Types
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Recognized string operation semantic.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
|
|
@ -56,9 +54,7 @@ pub struct SanitizerInfo {
|
|||
pub is_global: bool,
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Encoding/decoding transform types
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Category of encoding/decoding transform for symbolic modeling.
|
||||
///
|
||||
|
|
@ -141,9 +137,7 @@ pub struct TransformMethodInfo {
|
|||
pub operand_source: StringOperandSource,
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// String method classification
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Classify a callee as a recognized string method.
|
||||
///
|
||||
|
|
@ -481,9 +475,7 @@ fn classify_c(method: &str) -> Option<StringMethodInfo> {
|
|||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Encoding/decoding transform classification
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Classify a callee as a recognized encoding/decoding transform.
|
||||
///
|
||||
|
|
@ -757,9 +749,7 @@ fn classify_transform_ruby(callee: &str) -> Option<TransformMethodInfo> {
|
|||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Concrete encoding/decoding for witness rendering
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Apply encoding for witness rendering.
|
||||
///
|
||||
|
|
@ -939,9 +929,7 @@ fn hex_val(b: u8) -> Option<u8> {
|
|||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Arg extraction helpers
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Extract concrete pattern and replacement strings from args at given offset.
|
||||
///
|
||||
|
|
@ -960,9 +948,7 @@ fn has_concrete_index(args: &[SymbolicValue], offset: usize) -> bool {
|
|||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Concrete evaluation
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Evaluate a string operation on a concrete receiver string.
|
||||
///
|
||||
|
|
@ -986,9 +972,7 @@ pub fn evaluate_string_op_concrete(method: &StringMethod, receiver: &str) -> Opt
|
|||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Sanitizer detection
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Detect whether a Replace operation acts as a security sanitizer.
|
||||
///
|
||||
|
|
@ -1129,9 +1113,7 @@ fn is_global_replace(callee: &str, lang: Lang) -> bool {
|
|||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Tests
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
|
|
|||
|
|
@ -422,9 +422,7 @@ pub fn transfer_inst(
|
|||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Heap helpers
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Record a field store in the symbolic heap when the instruction defines
|
||||
/// a dotted path (e.g., `user.name`).
|
||||
|
|
@ -685,9 +683,7 @@ pub fn transfer_block(
|
|||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// String method dispatch
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Attempt to model a callee as a recognized string operation.
|
||||
///
|
||||
|
|
@ -809,9 +805,7 @@ fn try_transform_method(
|
|||
Some(SymbolicCallResult { value, tainted })
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Cross-file symbolic summary resolution
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Model a callee's return value from its SSA summary.
|
||||
///
|
||||
|
|
@ -969,9 +963,7 @@ fn resolve_callee_symbolically(
|
|||
model_from_summary(summary, arg_syms, all_operands, state, result_value)
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Tests
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
|
|
|||
|
|
@ -189,9 +189,7 @@ impl SymbolicValue {
|
|||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Smart constructors, all tree-building goes through these
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Build a binary arithmetic expression with concrete folding and depth bounding.
|
||||
///
|
||||
|
|
@ -316,9 +314,7 @@ pub fn mk_phi(operands: Vec<(BlockId, SymbolicValue)>) -> SymbolicValue {
|
|||
SymbolicValue::Phi(operands)
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// String operation smart constructors
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Build a `Trim` expression with concrete folding and depth bounding.
|
||||
pub fn mk_trim(s: SymbolicValue) -> SymbolicValue {
|
||||
|
|
@ -458,9 +454,7 @@ pub fn mk_decode(kind: super::strings::TransformKind, s: SymbolicValue) -> Symbo
|
|||
SymbolicValue::Decode(kind, Box::new(s))
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Display, human-readable witness strings
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Maximum length for the Display output before truncation.
|
||||
const MAX_DISPLAY_LEN: usize = 256;
|
||||
|
|
@ -538,9 +532,7 @@ fn display_inner(val: &SymbolicValue) -> String {
|
|||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Tests
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
|
|
|||
|
|
@ -17,9 +17,7 @@ use crate::taint::Finding;
|
|||
use super::state::SymbolicState;
|
||||
use super::value::SymbolicValue;
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Public API
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Extract a human-readable witness string for a confirmed finding.
|
||||
///
|
||||
|
|
@ -118,9 +116,7 @@ pub fn extract_witness(
|
|||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Helpers
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// When the sink expression is a `Call`, find the most informative tainted
|
||||
/// argument to use for witness generation instead of the opaque return value.
|
||||
|
|
@ -464,9 +460,7 @@ fn evaluate_concrete(expr: &SymbolicValue) -> String {
|
|||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Transform–sink mismatch detection
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Heuristic check: does a protective transform in the expression match
|
||||
/// the sink's vulnerability class?
|
||||
|
|
@ -529,9 +523,7 @@ fn cap_description(cap: Cap) -> &'static str {
|
|||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Tests
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
|
|
|||
|
|
@ -556,9 +556,7 @@ fn cross_file_sanitizer_resolved_via_global_summaries() {
|
|||
);
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Shared test helpers
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Parse Rust source bytes → FileCfg
|
||||
fn parse_rust(src: &[u8]) -> FileCfg {
|
||||
|
|
@ -777,9 +775,7 @@ fn cross_file_sink_cap_only_site_leaves_primary_location_none() {
|
|||
);
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Multi-file integration tests (real parsing, full pass-1 → pass-2 pipeline)
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn multi_file_source_to_sink_detected() {
|
||||
|
|
@ -1070,9 +1066,7 @@ fn multi_file_chain_source_sanitize_sink_across_files() {
|
|||
);
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Edge-case unit tests
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn sanitizer_strips_only_matching_bits() {
|
||||
|
|
@ -1435,9 +1429,7 @@ fn multiple_cross_file_sources_one_sanitised() {
|
|||
);
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Multi-language helpers and tests
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Parse source bytes for any supported language → FileCfg
|
||||
fn parse_lang(src: &[u8], slug: &str, ts_lang: tree_sitter::Language) -> FileCfg {
|
||||
|
|
@ -1956,9 +1948,7 @@ fn ruby_source_to_sink() {
|
|||
);
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Cross-language multi-file tests
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
//
|
||||
// Cross-language resolution now requires explicit InteropEdge declarations.
|
||||
// Without an edge, functions from different languages are never resolved ,
|
||||
|
|
@ -6158,9 +6148,7 @@ fn link_alternative_paths_three_way_group() {
|
|||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Typed call-graph devirtualisation (typed_call_receivers)
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// when a method call's receiver was constructed from a known
|
||||
/// constructor (`File::open` → `FileHandle`), the SSA-extraction
|
||||
|
|
|
|||
|
|
@ -19,9 +19,7 @@ use std::{
|
|||
thread,
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Internal constants / helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
type Paths = Vec<PathBuf>;
|
||||
|
||||
|
|
@ -84,7 +82,6 @@ fn build_overrides(root: &Path, cfg: &Config) -> ignore::overrides::Override {
|
|||
})
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
/// Walk `root` and send *batches* of paths through the returned channel.
|
||||
pub fn spawn_file_walker(root: &Path, cfg: &Config) -> (Receiver<Paths>, JoinHandle<()>) {
|
||||
let _span = tracing::info_span!("spawn_file_walker", root = %root.display()).entered();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue