Skip to content

Commit 9126b98

Browse files
hyperpolymathclaude
andcommitted
bench(assail): per-file UnboundedAllocation detector (Task #25)
Two new criterion benches for the word-boundary detector refactor: unbounded_detector_clean_file — 262 µs Synthetic file with tokio unbounded_channel + detector's own identifiers (has_unbounded_allocations) + bounded .take(LIMIT) read. Detector correctly does NOT fire. Exercises the negative (disarmed) path. unbounded_detector_dirty_file — 519 µs Bare `fn unbounded()` + unbounded std::fs::read_to_string. Detector correctly DOES fire. Exercises the positive path, which also walks all alarm-keyword regexes that are triggered. For reference: assail_self_scan — 55 ms (full src/ walk, ~85 files) clean_file — 262 µs (single-file equivalent ≈ 3 ms/file when amortised over a full walk) The regex-initialisation overhead is fully amortised: every regex is wrapped in OnceLock and compiled once per process. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent e199ef3 commit 9126b98

1 file changed

Lines changed: 79 additions & 0 deletions

File tree

benches/scan_bench.rs

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,84 @@ fn bench_self_scan(c: &mut Criterion) {
8181
});
8282
}
8383

84+
/// Benchmark the UnboundedAllocation heuristic end-to-end on a single
85+
/// file. After the substring -> word-boundary regex refactor (Task #25),
86+
/// this gives a per-file analysis number for tight dev loops — smaller
87+
/// than the full `assail_self_scan` which walks all of src/.
88+
///
89+
/// Uses `tempfile` to write a synthetic .rs file and analyse its
90+
/// parent directory; the per-iter overhead includes one stat() and
91+
/// one file open, which is the realistic cost path.
92+
fn bench_unbounded_allocation_detector(c: &mut Criterion) {
93+
use std::io::Write;
94+
95+
// Synthetic source that exercises the detector in both directions:
96+
// contains tokio's `unbounded_channel` (must NOT fire — word-boundary),
97+
// the detector's own `has_unbounded_allocations` identifier
98+
// (must NOT fire — trailing `_`), and a bounded `.take(LIMIT)` read.
99+
let clean_rust_source = r#"
100+
use std::io::Read;
101+
use tokio::sync::mpsc;
102+
103+
const READ_LIMIT: u64 = 64 * 1024 * 1024;
104+
105+
pub fn make_channel() -> mpsc::UnboundedSender<u8> {
106+
let (tx, _) = mpsc::unbounded_channel();
107+
tx
108+
}
109+
110+
pub fn load(path: &str) -> std::io::Result<String> {
111+
let mut buf = String::new();
112+
std::fs::File::open(path)?.take(READ_LIMIT).read_to_string(&mut buf)?;
113+
Ok(buf)
114+
}
115+
116+
pub fn analyze(body: &str) -> bool {
117+
let has_unbounded_allocations = body.contains("x");
118+
let unbounded_vec_patterns = body.len();
119+
has_unbounded_allocations && unbounded_vec_patterns > 0
120+
}
121+
"#;
122+
123+
let clean_dir = tempfile::tempdir().expect("tempdir");
124+
let clean_file = clean_dir.path().join("clean.rs");
125+
std::fs::File::create(&clean_file)
126+
.unwrap()
127+
.write_all(clean_rust_source.as_bytes())
128+
.unwrap();
129+
130+
c.bench_function("unbounded_detector_clean_file", |b| {
131+
b.iter(|| {
132+
let _ = black_box(panic_attack::assail::analyze(clean_dir.path()));
133+
})
134+
});
135+
136+
// Dirty source: bare `unbounded()` fn + unbounded fs::read_to_string.
137+
// Detector SHOULD fire. Positive-signal path.
138+
let dirty_rust_source = r#"
139+
pub fn unbounded() -> Vec<u8> {
140+
Vec::new()
141+
}
142+
143+
pub fn slurp(path: &str) -> std::io::Result<String> {
144+
std::fs::read_to_string(path)
145+
}
146+
"#;
147+
148+
let dirty_dir = tempfile::tempdir().expect("tempdir");
149+
let dirty_file = dirty_dir.path().join("dirty.rs");
150+
std::fs::File::create(&dirty_file)
151+
.unwrap()
152+
.write_all(dirty_rust_source.as_bytes())
153+
.unwrap();
154+
155+
c.bench_function("unbounded_detector_dirty_file", |b| {
156+
b.iter(|| {
157+
let _ = black_box(panic_attack::assail::analyze(dirty_dir.path()));
158+
})
159+
});
160+
}
161+
84162
/// Benchmark taint analysis engine
85163
fn bench_taint_analysis(c: &mut Criterion) {
86164
use panic_attack::kanren::core::FactDB;
@@ -164,6 +242,7 @@ criterion_group!(
164242
bench_language_detect,
165243
bench_language_family,
166244
bench_self_scan,
245+
bench_unbounded_allocation_detector,
167246
bench_taint_analysis,
168247
bench_rule_evaluation,
169248
bench_location_extraction,

0 commit comments

Comments
 (0)