mirror of
https://github.com/elicpeter/nyx.git
synced 2026-06-06 19:35:13 +02:00
* refactor: Update comments for clarity and add expectations.json files for performance metrics * feat: Implement FP guard for JS/TS local-collection receivers to suppress missing ownership checks * feat: Enhance Rust parameter handling to classify local collections and prevent false ownership checks * refactor: Simplify code formatting for better readability in multiple files * refactor: Improve UTF-8 sequence length handling and enhance clarity in loop iteration * feat: Update Java and Python patterns to include new security rules * refactor: Improve comment clarity and consistency across multiple Rust files * refactor: Simplify code formatting for improved readability in integration tests and module files * refactor: Improve comment formatting and enhance clarity in assertions across multiple files
113 lines
4.6 KiB
Rust
113 lines
4.6 KiB
Rust
//! Cross-file SCC joint fixed-point regression tests.
|
|
//!
|
|
//! These fixtures exercise SCCs whose mutual recursion *spans multiple
|
|
//! files*. A tighter `cross_file: bool` signal on `FileBatch` and a
|
|
//! matching cross-file unconverged-note prefix cover this path; the
|
|
//! pass-2 orchestrator iterates cross-file SCCs jointly via the
|
|
//! existing summary-snapshot convergence loop (which is monotone and
|
|
//! captures the transitive inline results produced per iteration).
|
|
//!
|
|
//! The assertions below lock down:
|
|
//!
|
|
//! * Cross-file SCCs converge, the required finding surfaces at the
|
|
//! caller.
|
|
//! * Iteration counts stay in a modest, pinned range (proves the cycle
|
|
//! actually exercised the SCC fix-point loop rather than resolving
|
|
//! via topological order).
|
|
//! * Sanitised cross-file cycles do not produce a finding at the caller
|
|
//! , the joint convergence carries the sanitizer fact back across the
|
|
//! cycle.
|
|
|
|
mod common;
|
|
|
|
use common::{scan_fixture_dir, validate_expectations};
|
|
use nyx_scanner::commands::scan::{last_scc_max_iterations, set_scc_fixpoint_cap_override};
|
|
use nyx_scanner::utils::config::AnalysisMode;
|
|
use std::path::Path;
|
|
use std::sync::Mutex;
|
|
|
|
fn fixture_path(name: &str) -> std::path::PathBuf {
|
|
Path::new(env!("CARGO_MANIFEST_DIR"))
|
|
.join("tests/fixtures")
|
|
.join(name)
|
|
}
|
|
|
|
/// Serialize tests that read `last_scc_max_iterations()` / mutate the
|
|
/// SCC cap override. Same guard pattern as `scc_convergence_tests.rs`.
|
|
static SCC_TEST_GUARD: Mutex<()> = Mutex::new(());
|
|
|
|
/// Two-file mutual recursion: `module_a::step_a ↔ module_b::step_b`
|
|
/// with a CMDI sink in `step_b`. The SCC spans two files so the
|
|
/// `FileBatch.cross_file` flag must fire, and the fixed-point loop
|
|
/// must iterate long enough that `step_a`'s summary reflects the
|
|
/// transitive `run_shell` sink reachable via `step_b`.
|
|
#[test]
|
|
fn two_file_mutual_recursion_reaches_transitive_sink() {
|
|
let _guard = SCC_TEST_GUARD.lock().unwrap_or_else(|e| e.into_inner());
|
|
set_scc_fixpoint_cap_override(0);
|
|
|
|
let dir = fixture_path("cross_file_scc_mutual_recursion");
|
|
let diags = scan_fixture_dir(&dir, AnalysisMode::Full);
|
|
|
|
validate_expectations(&diags, &dir);
|
|
|
|
// The 2-cycle should converge in very few iterations. Allow 0
|
|
// (no SCC loop needed, topo order already handled it) through 5
|
|
// (some monotone refinement churn). A higher number indicates the
|
|
// fix-point loop is churning near the cap.
|
|
let iters = last_scc_max_iterations();
|
|
assert!(
|
|
iters <= 5,
|
|
"2-file mutual-recursion SCC should converge in <= 5 iterations; got {iters}",
|
|
);
|
|
}
|
|
|
|
/// Three-way cross-file cycle: `node_a::forward_a → node_b::forward_b →
|
|
/// node_c::forward_c → node_a::forward_a`. All three files sit in the
|
|
/// same SCC. With `SCC_FIXPOINT_SAFETY_CAP = 64` the cycle converges
|
|
/// easily, but the iteration count must stay bounded, this test pins
|
|
/// the convergence envelope.
|
|
#[test]
|
|
fn three_file_cross_file_cycle_converges_within_bound() {
|
|
let _guard = SCC_TEST_GUARD.lock().unwrap_or_else(|e| e.into_inner());
|
|
set_scc_fixpoint_cap_override(0);
|
|
|
|
let dir = fixture_path("cross_file_scc_three_way_cycle");
|
|
let diags = scan_fixture_dir(&dir, AnalysisMode::Full);
|
|
|
|
validate_expectations(&diags, &dir);
|
|
|
|
// A 3-node cycle needs at most k=3 iterations for a fact at one
|
|
// edge to propagate around to every other summary, plus one more
|
|
// to detect fixed-point. Anything under 8 is healthy. Allow 0 as
|
|
// well (topo-order resolution without SCC loop) so this test does
|
|
// not become load-bearing on SCC-detection thresholds.
|
|
let iters = last_scc_max_iterations();
|
|
assert!(
|
|
iters <= 8,
|
|
"3-file cross-file cycle should converge in <= 8 iterations; got {iters}",
|
|
);
|
|
}
|
|
|
|
/// Cross-file recursion where every flow through the cycle passes
|
|
/// through a sanitizer. With joint fixed-point convergence the
|
|
/// summary for `stage_a` records `sanitizer_caps(SHELL_ESCAPE)` on its
|
|
/// parameter and the downstream CMDI sink is suppressed at the caller.
|
|
#[test]
|
|
fn recursive_with_sanitiser_suppresses_finding_at_caller() {
|
|
let _guard = SCC_TEST_GUARD.lock().unwrap_or_else(|e| e.into_inner());
|
|
set_scc_fixpoint_cap_override(0);
|
|
|
|
let dir = fixture_path("cross_file_scc_recursive_with_sanitiser");
|
|
let diags = scan_fixture_dir(&dir, AnalysisMode::Full);
|
|
|
|
// `expectations.json` forbids py.cmdi in driver.py, joint
|
|
// convergence must carry the sanitizer across the cycle.
|
|
validate_expectations(&diags, &dir);
|
|
|
|
let iters = last_scc_max_iterations();
|
|
assert!(
|
|
iters <= 6,
|
|
"2-file sanitised cycle should converge in <= 6 iterations; got {iters}",
|
|
);
|
|
}
|