fold/docs/research/benchmark-next-increments-audit-2026-06-03.md
2026-06-04 20:20:29 -04:00

361 lines
12 KiB
Markdown

# Benchmark Next Increments Audit
> Date: 2026-06-03
> Scope: what it takes to add hybrid goldens, Rust/Swift goldens,
> performance/RSS budget gates, and portable binary fixtures to Fold's maturity
> benchmark harness.
## Implementation status
This audit has been implemented in the current working tree:
- portable binary fixtures and `build_source` binary specs;
- merge-only hybrid golden runner;
- Rust/Swift frontend golden runner with structured skips;
- wall/CPU/RSS measurement and optional loose budget gates;
- umbrella summary wiring for all implemented suites.
## Current baseline
Implemented benchmark pieces:
- `scripts/benchmarks/compare_findings.py`
- validates report shape;
- checks physical locations;
- checks backend provenance;
- supports `must_report`, `must_not_report`, `min_total`, `max_total`, and
`max_by_rule`.
- `scripts/benchmarks/run_binary_goldens.py`
- runs `fold scan --mode binary --binary ...`;
- supports optional host-specific smokes via `skip_if_missing`;
- supports generated fixtures via `build_source`.
- `scripts/benchmarks/run_source_goldens.py`
- generates temporary `compile_commands.json`;
- runs `fold scan --mode source`.
- `scripts/benchmarks/run_hybrid_goldens.py`
- generates source compile DBs, resolves/builds binary fixtures, and runs
merge-only hybrid scans.
- `scripts/benchmarks/run_rust_swift_goldens.py`
- runs Rust/Swift frontend scans with structured missing-tool skips.
- `scripts/benchmarks/runner_common.py`
- records wall/CPU/RSS metrics and enforces optional budget gates.
- `scripts/benchmarks/summarize_maturity.py`
- one-command JSON umbrella over all implemented suites.
- Existing shipped specs include binary, source, hybrid, Rust, and Swift goldens.
The comparator is intentionally dependency-free and has a SARIF/JSON report view
only; runners handle scan invocation.
## 1. Hybrid golden runner
### What it needs
A new runner should mirror `run_source_goldens.py` plus `run_binary_goldens.py`:
- spec directory: `tests/benchmarks/hybrid/`;
- runner: `scripts/benchmarks/run_hybrid_goldens.py`;
- build or resolve a source compile DB;
- resolve a binary path;
- invoke:
```sh
fold scan --mode hybrid \
--compile-db <tmp compile_commands.json> \
--binary <fixture binary> \
--source-detectors <...> \
--binary-detectors <...> \
--hybrid-strategy merge-only \
--format json --output <report>
```
Initial target should be merge-only. Hint-exchange strategies remain future work.
### Fixture strategy
Use a tiny source fixture and matching tiny compiled binary fixture. The first
hybrid golden should not require exact source/binary correlation yet; it should
prove that both backends can contribute findings and provenance survives merge.
Candidate first fixture:
- source side: `tests/fixtures/security/div_const_zero.cpp` / `div_guarded.cpp`;
- binary side: existing `tests/fixtures/binary/bof_*` or a new compiled small
binary with a hardcoded string / simple null-deref heuristic;
- expected:
- at least one `backend=source` finding;
- at least one `backend=binary` or `backend=binary-*` finding;
- guarded source lookalike must not report;
- no locationless findings.
### Comparator gap
`compare_findings.py` can already match backend and rule IDs, but it cannot yet
assert aggregate backend presence such as `require_backends: ["source", "binary"]`.
Options:
1. encode with `must_report` entries that match known source and binary rules;
2. extend comparator with `expect.require_backends`.
Recommendation: add `require_backends` because hybrid provenance is the point of
this suite.
### Risks
- Exact binary findings may vary by architecture/compiler.
- Existing `bof_arm64`/`bof_x64` are committed binaries, but a generated fixture
would be more portable if built during the runner.
- Hybrid returns 1 if either backend emits actionable findings; runner already
accepts 0/1 patterns from source/binary runners.
### Acceptance criteria
- `scripts/benchmarks/run_hybrid_goldens.py --build build/release` runs at least
one spec.
- `summarize_maturity.py --all` includes `hybrid_goldens`.
- First spec proves source + binary backend provenance in one report.
- Spec fails closed on missing required inputs unless explicitly marked skippable.
## 2. Rust/Swift golden runner
### What it needs
A new language-frontend runner should cover `fold scan --mode rust` and
`fold scan --mode swift` without assuming both toolchains exist everywhere.
Proposed runner:
```text
scripts/benchmarks/run_frontend_goldens.py
```
or split runners:
```text
scripts/benchmarks/run_rust_swift_goldens.py
```
Spec directories:
```text
tests/benchmarks/rust/
tests/benchmarks/swift/
```
Spec fields:
- `mode`: `rust` or `swift`;
- `paths`: source file or crate/package directory;
- `detectors`;
- `env`: optional environment overrides such as `FOLD_RUST_BIN`;
- `skip_if_missing_tools`: true by default for Rust/Swift until CI images are
pinned.
### Existing fixture surface
Rust:
- `tests/fixtures/rust/security_crate/` exists;
- integration tests already locate `fold-rust` via `FOLD_RUST_BIN` or
`tools/fold-rust/target/release/fold-rust`;
- detectors include `rust-unsafe-api`, `rust-unchecked-index`,
`rust-path-traversal`, `rust-command-injection`, and `rust-crypto-misuse`.
Swift:
- `tests/fixtures/swift/Package.swift` and `Sources/fuzz_me.swift` exist;
- `fold-swift` is optional through Meson `-Dswift-frontend=true/false`;
- Swift mode is already routed in `scan_router` and forces serial execution.
### Comparator gaps
`compare_findings.py` is enough for initial Rust/Swift findings if specs use
`ruleId`, `backend`, and message regexes.
Possible extension later:
- `require_confidence`;
- `require_correlationKind`;
- per-frontend degradation counters if profile/report properties expose them.
### Risks
- Rust frontend depends on nightly/toolchain and `fold-rust` build output.
- Swift frontend depends on `swiftc` and platform availability.
- These suites should skip when tools are missing, but a skipped suite should be
visible in the maturity summary.
### Acceptance criteria
- Runner handles missing frontend binaries/toolchains as structured skips, not
false passes or hard crashes.
- At least one Rust spec checks a must-report unsafe API and a safe/missing rule
shape.
- At least one Swift spec checks a stable finding if current Swift detectors emit
one; otherwise the first Swift spec should be a report-shape/no-crash golden
with explicit `max_total` and provenance/location requirements for any emitted
findings.
- `summarize_maturity.py --all` includes Rust/Swift suite status.
## 3. Performance/RSS budget gates
### What it needs
Current runners record wall-clock seconds only. A maturity budget gate needs
structured resource measurement:
- wall time;
- CPU time when available;
- max RSS;
- optional Fold `--profile` JSON fields.
On macOS, `/usr/bin/time -l` gives maximum resident set size. On Linux,
`/usr/bin/time -v` gives maximum resident set size. Python `resource.getrusage`
can capture child maxrss but units differ by platform and it may aggregate over
children. Recommendation: implement a small cross-platform Python measurement
wrapper using `resource.getrusage(RUSAGE_CHILDREN)` plus optional
platform-specific `/usr/bin/time` fallback only if needed.
### Spec model
Add optional budget fields under `expect`:
```json
"budgets": {
"max_wall_seconds": 10.0,
"max_cpu_seconds": 20.0,
"max_rss_mb": 512,
"profile_max_total_wall_ms": 10000
}
```
Budget gates should start loose and informational for local work. CI can tighten
specific small fixtures once stable.
### Integration options
1. Embed measurement in each runner.
2. Create shared helper module used by all benchmark runners.
Recommendation: shared helper to avoid drift:
```text
scripts/benchmarks/runner_common.py
```
Potential contents:
- `run_fold_measured(cmd, cwd)`;
- `load_json`, `resolve_path`, `find_fold_binary`;
- resource result schema.
### Risks
- RSS is noisy across Apple Silicon/Linux and linked-r2 vs CLI-r2 modes.
- Cold caches make first run slower.
- Profile mode forces serial execution for source/Rust/Swift paths, so profile
timing should not be compared directly against non-profile timing unless the
spec opts in.
### Acceptance criteria
- Benchmark summaries include `wall_seconds`, `cpu_seconds`, and `rss_mb` when
available.
- Specs can set loose budgets and fail closed when exceeded.
- Initial budgets are applied only to small, deterministic source/binary
fixtures; host `/usr/bin/curl` smoke remains informational or skip-budgeted.
- Docs explain platform noise and how to update budgets.
## 4. More portable binary fixtures
### What it needs
The current real-binary curl smoke is useful but host-specific. Portable binary
fixtures should be compiled from checked-in C/C++ source during setup or runner
execution, or committed in controlled small forms for multiple architectures.
Recommended layout:
```text
tests/benchmarks/binary/fixtures/
guarded_division.c
fortified_wrappers.c
scanner_vs_writer.c
hardcoded_secret.c
build_binary_fixtures.py
```
Generated outputs should live under the benchmark output directory, not be
checked in unless there is a strong cross-toolchain reason.
### First fixture set
1. `guarded_division.c`
- contains actual machine division but guarded denominator;
- expected: no `fold.div-by-zero`.
2. `scanner_vs_writer.c`
- contains read-only scanners: `memchr`, `strstr`, `strchr`;
- contains controlled writer: `memcpy` or `strncpy` in safe shape;
- expected: no P-Code OOB false positives from scanner/import-stub adjacency.
3. `fortified_wrappers.c`
- compiled with `_FORTIFY_SOURCE=2` where supported;
- includes `__*_chk`-lowered patterns;
- expected: fortified wrappers are not normalized to unchecked sinks.
4. `hardcoded_secret.c`
- stable positive control for binary string/credential finding;
- expected: at least one `fold.hardcoded-credential` backend binary finding.
### Runner support
Extend `run_binary_goldens.py` or add helper support for specs with
`build_source`:
```json
{
"name": "binary_guarded_division",
"build_source": "fixtures/guarded_division.c",
"compiler": "cc",
"cflags": ["-O1"],
"detectors": "security",
"expect": { ... }
}
```
If compiler is missing, fail closed for portable fixtures; unlike curl, these are
core benchmark assets.
### Risks
- `_FORTIFY_SOURCE` behavior varies by libc/platform; on macOS it may not lower
exactly like glibc.
- Division instruction selection varies under optimization.
- r2/lifter behavior can vary by architecture.
Mitigations:
- Use separate specs for portable generic fixtures and platform-specific
fortified behavior.
- Include relaxed exact-count expectations; prefer `must_not_report` for known
FP signatures and stable positive controls for one finding.
- Use `--binary-max-functions` small caps only when function ordering is stable;
otherwise scan all tiny fixture functions.
### Acceptance criteria
- At least two generated binary fixtures are built and scanned in the binary
golden suite.
- One fixture is a stable positive control.
- One fixture locks a P-Code false-positive shape independent of `/usr/bin/curl`.
- Curl smoke remains optional and complementary.
## Recommended sequencing
1. Portable binary fixtures first: they also feed hybrid and perf suites.
2. Hybrid runner using one portable binary fixture plus existing source fixtures.
3. Performance/RSS budget infrastructure around existing source/binary runners.
4. Rust/Swift runner after deciding skip policy and stable Swift expectations.
This sequencing gives hybrid/perf deterministic inputs before adding toolchain-
optional frontend complexity.