nyx/tests/cross_file_context_off_tests.rs
Eli Peter a438886217
Python fp and docs updtes (#58)
* 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
2026-04-29 19:53:34 -04:00

106 lines
4.3 KiB
Rust

//! Context-sensitive off-path tests: with `NYX_CONTEXT_SENSITIVE=0`
//! (the runtime switch for the inline context-sensitive pipeline) the
//! analyser falls back to summary-based resolution across file
//! boundaries. Cross-file inline is *strictly additive* over this
//! path: the four fixtures in `tests/fixtures/cross_file_context_*`
//! must still satisfy their `expectations.json` under summary-only
//! resolution, and the sanitiser fixture must remain free of taint
//! findings.
//!
//! This binary is split from `cross_file_context_tests.rs` because
//! Cargo compiles each `tests/*.rs` file into its own test binary ,
//! separate processes, so the `NYX_CONTEXT_SENSITIVE` env flip here
//! does not race against the default-on tests running in parallel.
//!
//! The switch is read by `AnalysisOptions::current()` via the legacy
//! env-var fallback (no `install()` call happens in a test binary), so
//! toggling the env var takes effect on every scan through this
//! process.
mod common;
use common::{scan_fixture_dir, validate_expectations};
use nyx_scanner::commands::scan::Diag;
use nyx_scanner::utils::config::AnalysisMode;
use std::path::{Path, PathBuf};
fn fixture_path(name: &str) -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR"))
.join("tests")
.join("fixtures")
.join(name)
}
fn count_prefix(diags: &[Diag], prefix: &str) -> usize {
diags.iter().filter(|d| d.id.starts_with(prefix)).count()
}
/// Install CS-off for this entire test binary. Because every test
/// file compiles into its own binary, no sibling test sees this flip.
fn disable_context_sensitive() {
// SAFETY: env-var mutation is unsound under concurrent reads; we
// call this at the start of every test in this binary *before*
// `scan_no_index` reaches `AnalysisOptions::current()`. No other
// thread is observing the env var concurrently because Cargo
// spawns one thread per test and each one sets the same value.
unsafe {
std::env::set_var("NYX_CONTEXT_SENSITIVE", "0");
}
}
/// Cross-file inline must be strictly additive: the summary path was
/// already correct for this fixture before cross-file inline landed,
/// and the inline override must not drop recall when we disable it.
#[test]
fn two_call_sites_still_passes_without_context_sensitivity() {
disable_context_sensitive();
let dir = fixture_path("cross_file_context_two_call_sites");
let diags = scan_fixture_dir(&dir, AnalysisMode::Full);
validate_expectations(&diags, &dir);
}
/// The cross-file sanitiser regression guard: with summary-only
/// resolution the `xss` library is still a registered sanitiser and
/// the taint finding must not surface. This also rules out the
/// possibility that cross-file inline was the only thing suppressing
/// the finding in the default-on tests.
#[test]
fn sanitizer_still_clean_without_context_sensitivity() {
disable_context_sensitive();
let dir = fixture_path("cross_file_context_sanitizer");
let diags = scan_fixture_dir(&dir, AnalysisMode::Full);
assert_eq!(
count_prefix(&diags, "taint-unsanitised-flow"),
0,
"sanitiser fixture must remain clean under summary-only \
resolution. Full diags: {:?}",
diags
.iter()
.map(|d| format!("{}:{}:{}", d.path, d.line, d.id))
.collect::<Vec<_>>()
);
}
/// Deep chain (A->B->C) finding persists under summary-only
/// resolution via the AST-pattern `py.cmdi` path. Cross-file inline
/// does not change the AST-pattern suite, so this assertion is a
/// simple regression guard.
#[test]
fn deep_chain_still_passes_without_context_sensitivity() {
disable_context_sensitive();
let dir = fixture_path("cross_file_context_deep_chain");
let diags = scan_fixture_dir(&dir, AnalysisMode::Full);
validate_expectations(&diags, &dir);
}
/// Callback fixture produces its finding via the summary-level
/// callback-argument mechanism (apply's summary + direct
/// `child_process.exec` arg). CS=off must keep that finding because
/// the mechanism lives outside the inline pipeline.
#[test]
fn callback_fixture_still_passes_without_context_sensitivity() {
disable_context_sensitive();
let dir = fixture_path("cross_file_context_callback");
let diags = scan_fixture_dir(&dir, AnalysisMode::Full);
validate_expectations(&diags, &dir);
}