diff --git a/benches/scan_bench.rs b/benches/scan_bench.rs index 748a0f5..bcd4c44 100644 --- a/benches/scan_bench.rs +++ b/benches/scan_bench.rs @@ -5,8 +5,9 @@ //! //! Measures: language detection, pattern matching, full analysis pipeline. -use criterion::{black_box, criterion_group, criterion_main, Criterion}; +use criterion::{criterion_group, criterion_main, Criterion}; use panic_attack::types::Language; +use std::hint::black_box; /// Benchmark language detection from file extension fn bench_language_detect(c: &mut Criterion) { @@ -223,6 +224,7 @@ fn bench_statistics_calculation(c: &mut Criterion) { unsafe_blocks: 5, panic_sites: 0, unwrap_calls: 50, + safe_unwrap_calls: 0, allocation_sites: 12, io_operations: 4, threading_constructs: 3, diff --git a/src/assail/analyzer.rs b/src/assail/analyzer.rs index 082970b..eb61ead 100644 --- a/src/assail/analyzer.rs +++ b/src/assail/analyzer.rs @@ -3966,7 +3966,7 @@ impl Analyzer { // Detect function definitions if line.trim().starts_with("fn ") && !line.trim().starts_with("fn test") { - if let Some(func_name) = line.trim().split_whitespace().nth(1) { + if let Some(func_name) = line.split_whitespace().nth(1) { let func_name = func_name.split('(').next().unwrap_or(func_name); current_function = func_name.to_string(); function_calls @@ -5174,12 +5174,11 @@ impl Analyzer { } } - Language::Erlang => { - if content.contains("-behaviour(gen_server)") - || content.contains("-behaviour(supervisor)") - { - frameworks.insert(Framework::OTP); - } + Language::Erlang + if (content.contains("-behaviour(gen_server)") + || content.contains("-behaviour(supervisor)")) => + { + frameworks.insert(Framework::OTP); } Language::Go => { @@ -6007,7 +6006,7 @@ func main() { && wp .location .as_deref() - .map_or(false, |loc| loc.contains("node_modules")) + .is_some_and(|loc| loc.contains("node_modules")) }) .collect(); diff --git a/src/assemblyline.rs b/src/assemblyline.rs index a541bfc..9749ef4 100644 --- a/src/assemblyline.rs +++ b/src/assemblyline.rs @@ -432,7 +432,7 @@ pub fn run_with_cache( .count(); // Sort by weak point count descending (riskiest repos first) - results.sort_by(|a, b| b.weak_point_count.cmp(&a.weak_point_count)); + results.sort_by_key(|r| std::cmp::Reverse(r.weak_point_count)); // Apply filters if config.findings_only { diff --git a/src/bridge/lockfile.rs b/src/bridge/lockfile.rs index 9d428ee..7f237f6 100644 --- a/src/bridge/lockfile.rs +++ b/src/bridge/lockfile.rs @@ -17,10 +17,12 @@ use std::path::Path; /// order. All successful parses are merged into a single list. Errors from /// individual parsers are logged as warnings and skipped so one malformed /// lockfile does not abort triage of the whole project. +type LockfileParser = fn(&Path) -> Result>; + pub fn discover_and_parse(dir: &Path) -> Vec { let mut all = Vec::new(); - let candidates: &[(&str, fn(&Path) -> Result>)] = &[ + let candidates: &[(&str, LockfileParser)] = &[ ("Cargo.lock", parse_cargo_lock), ("mix.lock", parse_mix_lock), ("package-lock.json", parse_package_lock_json), @@ -66,7 +68,7 @@ pub fn parse_cargo_lock(path: &Path) -> Result> { if let (Some(name), Some(version)) = (current_name.take(), current_version.take()) { if current_source .as_ref() - .map_or(false, |s| s.contains("registry")) + .is_some_and(|s| s.contains("registry")) { deps.push(LockedDependency { name, @@ -94,7 +96,7 @@ pub fn parse_cargo_lock(path: &Path) -> Result> { if let (Some(name), Some(version)) = (current_name, current_version) { if current_source .as_ref() - .map_or(false, |s| s.contains("registry")) + .is_some_and(|s| s.contains("registry")) { deps.push(LockedDependency { name, diff --git a/src/bridge/reachability.rs b/src/bridge/reachability.rs index b9d118d..1d50a6d 100644 --- a/src/bridge/reachability.rs +++ b/src/bridge/reachability.rs @@ -54,7 +54,7 @@ pub fn check_reachability(project_dir: &Path, crate_name: &str) -> Result, } +impl Default for MitigationRegistry { + fn default() -> Self { + Self::new() + } +} + impl MitigationRegistry { /// Create an empty registry. pub fn new() -> Self { diff --git a/src/main.rs b/src/main.rs index 1244e09..90a2210 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2250,8 +2250,8 @@ fn run_main() -> Result<()> { println!("Run `panic-attack bridge triage --register` to populate."); } else { println!( - "{:<12} {:<20} {:<15} {:<15} {}", - "ID", "ADVISORY", "PACKAGE", "STATUS", "ACTION" + "{:<12} {:<20} {:<15} {:<15} ACTION", + "ID", "ADVISORY", "PACKAGE", "STATUS" ); println!("{}", "-".repeat(80)); for entry in ®istry.entries { diff --git a/src/mass_panic/imaging.rs b/src/mass_panic/imaging.rs index 965569e..4e00f81 100644 --- a/src/mass_panic/imaging.rs +++ b/src/mass_panic/imaging.rs @@ -198,7 +198,7 @@ pub fn build_image(report: &AssemblylineReport) -> SystemImage { .into_iter() .map(|(name, count)| CategoryCount { name, count }) .collect(); - cats.sort_by(|a, b| b.count.cmp(&a.count)); + cats.sort_by_key(|c| std::cmp::Reverse(c.count)); cats } else { Vec::new() diff --git a/src/storage/mod.rs b/src/storage/mod.rs index a01a27e..91f0d02 100644 --- a/src/storage/mod.rs +++ b/src/storage/mod.rs @@ -451,7 +451,7 @@ pub fn push_hexad_http(hexad: &PanicAttackHexad, gateway_url: &str) -> Result= 200 && status < 300 { + if (200..300).contains(&status) { Ok(body) } else { Err(anyhow!("VeriSimDB returned {}: {}", status, body)) @@ -580,16 +580,16 @@ pub fn push_hexad_http_with_retry(hexad: &PanicAttackHexad, gateway_url: &str) - std::time::Duration::from_secs(2), std::time::Duration::from_secs(4), ]; - let max_attempts: usize = 3; + let max_attempts = delays.len(); let mut last_err: Option = None; - for attempt in 0..max_attempts { + for (attempt, delay) in delays.iter().enumerate() { match push_hexad_http(hexad, gateway_url) { Ok(body) => return Ok(body), Err(e) => { last_err = Some(e); if attempt < max_attempts - 1 { - std::thread::sleep(delays[attempt]); + std::thread::sleep(*delay); } } } @@ -632,7 +632,7 @@ pub fn push_hexads_batch(hexads: &[PanicAttackHexad], gateway_url: &str) -> Resu results.push(body); } Ok(results) - } else if status >= 200 && status < 300 { + } else if (200..300).contains(&status) { Ok(vec![read_body(response)]) } else { let body = read_body(response); @@ -669,7 +669,7 @@ pub fn query_hexads(gateway_url: &str, limit: usize) -> Result= 200 && status < 300 { + if (200..300).contains(&status) { let hexads: Vec = serde_json::from_str(&body) .map_err(|e| anyhow!("Failed to parse VeriSimDB response: {}", e))?; Ok(hexads) @@ -708,7 +708,7 @@ pub fn check_gateway(gateway_url: &str) -> bool { let is_healthy = match builder.call() { Ok(resp) => { let s = resp.status().as_u16(); - s >= 200 && s < 300 + (200..300).contains(&s) } Err(_) => false, }; diff --git a/tests/aspect_tests.rs b/tests/aspect_tests.rs index a22798e..fee95b5 100644 --- a/tests/aspect_tests.rs +++ b/tests/aspect_tests.rs @@ -227,7 +227,7 @@ fn aspect_parallel_analysis_correctness() { .collect(); // Using rayon parallel iterator (same as assemblyline) - let _results: Vec<_> = files.par_iter().map(|path| assail::analyze(path)).collect(); + let _results: Vec<_> = files.par_iter().map(assail::analyze).collect(); // Should complete without data races or panics // (In practice, rayon + Rust's type system ensure this) diff --git a/tests/panll_tests.rs b/tests/panll_tests.rs index ea24eca..de03269 100644 --- a/tests/panll_tests.rs +++ b/tests/panll_tests.rs @@ -220,6 +220,7 @@ fn test_panll_export_constraints_from_failed_attacks() -> Result<(), Box Result<(), Box 0); + // Should not panic when iterating chars across multi-byte boundaries. + let char_count = unicode_content.chars().count(); + assert!(char_count > 0); } diff --git a/tests/report_tests.rs b/tests/report_tests.rs index 4352179..2ae9ffb 100644 --- a/tests/report_tests.rs +++ b/tests/report_tests.rs @@ -60,6 +60,7 @@ fn make_assail_report() -> AssailReport { fn make_attack_result(axis: AttackAxis, success: bool, crashes: usize) -> AttackResult { let crash_reports: Vec = (0..crashes) .map(|_| CrashReport { + schema_version: "2.5".to_string(), timestamp: "2026-03-01T00:00:00Z".to_string(), signal: Some("SIGSEGV".to_string()), backtrace: None, diff --git a/tests/seam_contract_tests.rs b/tests/seam_contract_tests.rs index 38da0c9..87c08c6 100644 --- a/tests/seam_contract_tests.rs +++ b/tests/seam_contract_tests.rs @@ -22,9 +22,9 @@ //! - **Schema version sentinel** (all consumers): //! reads `schema_version` to detect incompatible changes -use panic_attack::abduct::{self, AbductReport}; -use panic_attack::amuck::{self, AmuckReport}; -use panic_attack::axial::{self, AxialReport}; +use panic_attack::abduct::AbductReport; +use panic_attack::amuck::AmuckReport; +use panic_attack::axial::AxialReport; use panic_attack::types::*; use serde_json::Value; @@ -433,8 +433,7 @@ fn old_amuck_report_without_schema_version_deserializes_with_default() { "combinations_run": 0, "outcomes": [] }"#; - let report: AmuckReport = - serde_json::from_str(json_str).expect("deserialize old amuck report"); + let report: AmuckReport = serde_json::from_str(json_str).expect("deserialize old amuck report"); assert_eq!( report.schema_version, "2.5", "old AmuckReport missing schema_version must default to '2.5'" @@ -490,8 +489,7 @@ fn old_axial_report_without_schema_version_deserializes_with_default() { "observed_reports": 0, "language": "en" }"#; - let report: AxialReport = - serde_json::from_str(json_str).expect("deserialize old axial report"); + let report: AxialReport = serde_json::from_str(json_str).expect("deserialize old axial report"); assert_eq!( report.schema_version, "2.5", "old AxialReport missing schema_version must default to '2.5'"