Files
trailofbits__skills/plugins/rust-review/scripts/test_split.py
Andrea Cappa (zi0Black) cfe5d7b161 Rust review plugin (#178)
* rust-review: add Rust security review plugin

Add the rust-review plugin: a comprehensive Rust security review skill
with clustered finders covering memory safety, concurrency/data races,
panic-induced DoS, FFI/cross-language boundaries, error handling,
resource handling, async runtime, and static hygiene.

Includes worker, dedup-judge, fp-judge, and planner agents, SARIF
generation with rule descriptions and regression tests, deterministic
cluster chunking, and Codex skills mapping. Versioned at 1.0.0 and
registered in the marketplace, CODEOWNERS, and root README.

* c-review: backport rust-review protocol fixes and planner chunking

Port the language-agnostic fixes made while building rust-review (which
was ported from c-review) back into c-review:

- worker/fp-judge: force findings, coverage gate, and REPORT.md to disk
  via Write instead of returning content in the reply (orchestrator
  context-bloat hardening); add a pre-complete file-existence check.
- worker: move the cache-primer block below the normal self-check and
  pre-work budget so a non-primer worker does not start under a global
  "no tool calls" rule.
- planner: add --max-passes-per-worker (default 4) with deterministic
  split_oversized_clusters chunking; skill passes the flag and documents
  the chunked-subset worker rule.
- scripts: add test_split.py and test_generate_sarif.py regression
  tests. The SARIF test caught a missing RULE_DESCRIPTIONS entry for
  uninitialized-data, now added.

Bump c-review to 1.2.0.

* c-review/rust-review: validate artifacts, index-aware SARIF, protocol cleanups

- Add validate_artifacts.py (+ tests) to both plugins to check worker
  shard, coverage, and finding files before accepting completions.
- generate_sarif.py now reads the canonical findings-index.txt when
  present, falling back to findings/*.md only if the index is absent.
- Merge the worker step-6 verification paragraphs and drop orchestrator
  -internal Phase 7 / plan.json jargon in favor of worker-facing stakes.
- Tighten uninitialized-read-finder guidance: primitive integers still
  require initialization.

* rust-review/c-review: per-cluster max_passes_per_worker override

Lets output-heavy clusters declare a smaller manifest-level
max_passes_per_worker so each expensive pass group gets its own worker,
validated by a single shared cluster_max_passes_per_worker helper and
honored by split_oversized_clusters via an explicit override (0 is
rejected rather than silently falling back to the global cap). rust-review
opts in concurrency-locking and recursion-dos; c-review ports the
capability for parity. validate_artifacts now accepts grouped or repeated
--claimed-count values.

* rust-review: broaden bug-class coverage with capability-gated clusters

Add layout-safety, input-os-safety, and info-disclosure clusters behind new
has_packed_repr / has_fs_io capability gates so packed-repr, path, and
pointer-exposure passes only run where they apply, and gate unsafe-only passes
behind has_unsafe to cut noise on safe crates.

Extend existing clusters with new bug classes: RefCell double-borrow panics,
unflushed BufWriter, string-comparison bypasses, serialize_struct mismatches,
nondeterminism, in-collection key mutation, and destructor-skip cleanup leaks.

Fix detector regexes that missed or over-matched real Rust (packed-field
borrows, RefCell try_borrow_mut, HashMap substrings, path push, packed inner
attrs, fs/path probes) and add a regression test pinning them to snippets.

* fix dedup

* safety-net check for REPORT.md

* on-disk data -> shards reconciliation

* on-disk data -> shards reconciliation - v2

* ls -> glob

* memory-safety gate

* path validation

* fix numbers/counting

* rm PACKEDREF from FFI cluster prompt, it is in layout-safety

* fix unsafe-boundary count

* minor fixes for prompts

* do not filter unknown-severity findings, just mark them as such

* fix minor behavior changes in worker

* Correctness:
- generate_sarif: clamp startLine >=1 (`:0` produced schema-invalid SARIF)
- generate_sarif: don't drop a judged survivor with blank severity
- dedup-judge: Tier-2 carry-forward so a primary can't be demoted/orphaned
- dedup-judge: crash-recovery unions shards with findings/*.md (empty-shard trap)

Robustness:
- generate_sarif: skip frontmatter-less files; add originalUriBaseIds

Contracts:
- SKILL: gate dedup-judge before fp-judge (prevent concurrent-spawn race)
- worker: verbatim coverage cells; sub_prompt_paths omitted-not-empty;
  skip_subclasses reserved; Codebase comma format

* improve prompts regexes, add missing deconflictions

* prompt factual fixes

* fix dozen of small prompt inconsistencies and add missing sections

* more prompt fixes, fix retry guard in SKILL, small fixes in agents

* dozen more small fixes

* final regex fixes

* fixes from rust to c-review

* agents cannot use write tool for reports (strange cc limitation) - bypass via bash

* spawnings agents is capped to 20 - explicit handling for that

* fix glob -> read (glob is blocked for agents that has also bash)

* fix regex patterns to work with grep

* soften output requirements - they were violated anyway

* consolidated clusters are no longer chunked — one worker owns the whole cluster, builds its shared Phase-A inventory once, and runs every phase

* fix judge finding counting and low-severity guidance

* fix metadata

* small fix for skipped findings

* Carry forward guard for `also_known_as` bucket

* Gracefully handle parse_frontmatter error

* Extend has_ffi coverage

* Broader gate for has_concurrency

* Update FFI-safe layout regex to support C, C+packed, and C+u32 in unsafe-boundary and dyn-trait-ffi-finder prompts

* Small refine of regex patterns

* Improve regex patterns for recursive type detection to include Mutex and RwLock

* rm global .codex/rust-review

* backport fixes to c-review

* merge changes

* Backport SARIF merge-survivor + malformed-frontmatter guards to c-review, mark missing locations, fix prompt-regex test extractor, and harden planner/validator scripts across both review plugins

* fix pytest

* fix global gitignore, adds / and ruff_cache

* small fixes from pr-review

* small fixes from pr-review - 2

* fix copilot finding

---------

Co-authored-by: GrosQuildu <e2.8a.95@gmail.com>
2026-06-30 11:14:46 -04:00

301 lines
10 KiB
Python

"""Unit tests for the cluster-chunking helper in build_run_plan.py.
The helper partitions any **non-consolidated** cluster whose `passes` list exceeds
`max_passes_per_worker` into contiguous, order-preserving chunks. Each chunk
becomes its own pseudo-cluster entry, carrying the source cluster's prompt
path and `consolidated` flag, with a `-{i}` suffix appended to its
cluster_id (1-indexed). Clusters that fit within the threshold pass through
unchanged. **Consolidated clusters are never chunked** (one worker owns all
phases so the shared Phase-A inventory grounds every phase).
"""
from __future__ import annotations
import pytest
from build_run_plan import build_selection, split_oversized_clusters
def _mk_cluster(
cid: str,
n_passes: int,
*,
consolidated: bool = False,
max_passes_per_worker: int | None = None,
) -> dict:
"""Construct a synthetic cluster entry matching build_selection()'s output shape."""
cluster = {
"cluster_id": cid,
"consolidated": consolidated,
"cluster_prompt": f"/abs/prompts/clusters/{cid}.md",
"passes": [
{"bug_class": f"{cid}-bc-{i}", "prefix": f"{cid.upper()}{i}"} for i in range(n_passes)
],
}
if max_passes_per_worker is not None:
cluster["max_passes_per_worker"] = max_passes_per_worker
return cluster
# --- Pass-through (single-chunk) cases ---------------------------------------
def test_pass_through_k_equals_one():
"""K=1 < N=4 → 1 chunk, bare cluster_id, byte-identical to input."""
src = [_mk_cluster("small", 1)]
out = split_oversized_clusters(src, max_passes=4)
assert out == src
def test_pass_through_k_equals_n():
"""K=4 == N=4 → still 1 chunk, bare cluster_id, no suffix."""
src = [_mk_cluster("exact", 4)]
out = split_oversized_clusters(src, max_passes=4)
assert len(out) == 1
assert out[0]["cluster_id"] == "exact"
assert len(out[0]["passes"]) == 4
def test_pass_through_preserves_consolidated_flag():
"""The `consolidated` flag must round-trip through the splitter even on pass-through."""
src = [_mk_cluster("c", 3, consolidated=True)]
out = split_oversized_clusters(src, max_passes=4)
assert out[0]["consolidated"] is True
# --- Split cases -------------------------------------------------------------
def test_split_k_5_n_4_yields_4_plus_1():
src = [_mk_cluster("big", 5)]
out = split_oversized_clusters(src, max_passes=4)
assert [c["cluster_id"] for c in out] == ["big-1", "big-2"]
assert [len(c["passes"]) for c in out] == [4, 1]
def test_split_k_8_n_4_yields_4_plus_4():
src = [_mk_cluster("bigger", 8)]
out = split_oversized_clusters(src, max_passes=4)
assert [c["cluster_id"] for c in out] == ["bigger-1", "bigger-2"]
assert [len(c["passes"]) for c in out] == [4, 4]
def test_split_k_9_n_4_yields_4_4_1():
src = [_mk_cluster("huge", 9)]
out = split_oversized_clusters(src, max_passes=4)
assert [c["cluster_id"] for c in out] == ["huge-1", "huge-2", "huge-3"]
assert [len(c["passes"]) for c in out] == [4, 4, 1]
def test_split_preserves_pass_order():
"""Chunk i must contain passes[(i-1)*N : i*N] in original manifest order."""
src = [_mk_cluster("ordered", 9)]
out = split_oversized_clusters(src, max_passes=4)
expected_bcs = [f"ordered-bc-{i}" for i in range(9)]
flat = [p["bug_class"] for c in out for p in c["passes"]]
assert flat == expected_bcs
def test_consolidated_cluster_never_chunks():
"""Consolidated clusters are exempt from chunking — one worker owns all phases
so the shared Phase-A inventory grounds every phase (it would otherwise be
rebuilt per chunk, which workers skip in practice)."""
src = [_mk_cluster("share", 8, consolidated=True)]
out = split_oversized_clusters(src, max_passes=4)
assert len(out) == 1
assert out[0]["cluster_id"] == "share" # bare id, no -1/-2 suffix
assert out[0]["consolidated"] is True
assert out[0]["cluster_prompt"] == "/abs/prompts/clusters/share.md"
assert len(out[0]["passes"]) == 8
def test_consolidated_cluster_ignores_mppw_override():
"""An mppw override on a consolidated cluster is validated but does not chunk it."""
src = [_mk_cluster("share", 6, consolidated=True, max_passes_per_worker=1)]
out = split_oversized_clusters(src, max_passes=4)
assert len(out) == 1
assert out[0]["cluster_id"] == "share"
assert len(out[0]["passes"]) == 6
def test_cluster_override_splits_below_global_max():
src = [_mk_cluster("output-heavy", 3, max_passes_per_worker=1)]
out = split_oversized_clusters(src, max_passes=4)
assert [c["cluster_id"] for c in out] == [
"output-heavy-1",
"output-heavy-2",
"output-heavy-3",
]
assert [len(c["passes"]) for c in out] == [1, 1, 1]
def test_cluster_override_does_not_affect_other_clusters():
src = [
_mk_cluster("output-heavy", 3, max_passes_per_worker=1),
_mk_cluster("normal", 3),
]
out = split_oversized_clusters(src, max_passes=4)
assert [c["cluster_id"] for c in out] == [
"output-heavy-1",
"output-heavy-2",
"output-heavy-3",
"normal",
]
def test_consolidated_override_ignored_passes_still_filtered(tmp_path):
prompt_path = tmp_path / "prompts" / "clusters" / "heavy.md"
prompt_path.parent.mkdir(parents=True)
prompt_path.write_text("# heavy\n", encoding="utf-8")
manifest = {
"version": 1,
"clusters": [
{
"cluster_id": "heavy",
"prompt": "prompts/clusters/heavy.md",
"consolidated": True,
"max_passes_per_worker": 1,
"gate": "always",
"passes": [
{
"bug_class": "filtered",
"prefix": "FILTERED",
"requires": ["has_unsafe"],
},
{"bug_class": "kept-a", "prefix": "KEPTA"},
{"bug_class": "kept-b", "prefix": "KEPTB"},
],
}
],
}
selected = build_selection(
manifest,
plugin_root=tmp_path,
flags={
"has_unsafe": False,
"has_ffi": False,
"has_concurrency": False,
"has_async": False,
"has_packed_repr": False,
"has_fs_io": False,
},
threat_model="REMOTE",
)
out = split_oversized_clusters(selected, max_passes=4)
# Consolidated cluster: the mppw override is ignored (no chunking), but pass
# filtering still applies — the `requires: has_unsafe` pass is dropped.
assert [c["cluster_id"] for c in out] == ["heavy"]
assert [[p["prefix"] for p in c["passes"]] for c in out] == [["KEPTA", "KEPTB"]]
@pytest.mark.parametrize("override", [False, "1", 0, -1])
def test_build_selection_rejects_invalid_manifest_override(tmp_path, override):
prompt_path = tmp_path / "prompts" / "clusters" / "heavy.md"
prompt_path.parent.mkdir(parents=True)
prompt_path.write_text("# heavy\n", encoding="utf-8")
manifest = {
"version": 1,
"clusters": [
{
"cluster_id": "heavy",
"prompt": "prompts/clusters/heavy.md",
"consolidated": True,
"max_passes_per_worker": override,
"gate": "always",
"passes": [{"bug_class": "kept", "prefix": "KEPT"}],
}
],
}
with pytest.raises(SystemExit) as excinfo:
build_selection(
manifest,
plugin_root=tmp_path,
flags={
"has_unsafe": False,
"has_ffi": False,
"has_concurrency": False,
"has_async": False,
"has_packed_repr": False,
"has_fs_io": False,
},
threat_model="REMOTE",
)
assert excinfo.value.code == 2
@pytest.mark.parametrize("override", [False, "1", 0, -1])
def test_split_rejects_invalid_standalone_override(override):
src = [_mk_cluster("invalid", 3, max_passes_per_worker=override)]
with pytest.raises(ValueError, match="positive integer"):
split_oversized_clusters(src, max_passes=4)
# --- Identity (disable) case -------------------------------------------------
def test_max_passes_zero_is_identity_no_suffix():
"""N=0 is the explicit 'disable chunking' sentinel — pass through with bare ids."""
src = [_mk_cluster("a", 8), _mk_cluster("b", 3, max_passes_per_worker=1)]
out = split_oversized_clusters(src, max_passes=0)
assert out == src
# --- Multiple clusters in one call -------------------------------------------
def test_mixed_input_handles_each_cluster_independently():
src = [
_mk_cluster("small", 2), # pass-through
_mk_cluster("split-me", 6), # 4 + 2
_mk_cluster("exact", 4), # pass-through
_mk_cluster("huge", 9), # 4 + 4 + 1
]
out = split_oversized_clusters(src, max_passes=4)
cids = [c["cluster_id"] for c in out]
sizes = [len(c["passes"]) for c in out]
assert cids == ["small", "split-me-1", "split-me-2", "exact", "huge-1", "huge-2", "huge-3"]
assert sizes == [2, 4, 2, 4, 4, 4, 1]
def test_order_of_source_clusters_is_preserved():
"""The relative ordering of source clusters in the input list survives splitting."""
src = [
_mk_cluster("alpha", 8),
_mk_cluster("beta", 3),
_mk_cluster("gamma", 5),
]
out = split_oversized_clusters(src, max_passes=4)
cids = [c["cluster_id"] for c in out]
assert cids == ["alpha-1", "alpha-2", "beta", "gamma-1", "gamma-2"]
# --- Determinism -------------------------------------------------------------
def test_same_input_same_output_repeated_calls():
src = [_mk_cluster("d", 7)]
out1 = split_oversized_clusters(src, max_passes=4)
out2 = split_oversized_clusters(src, max_passes=4)
assert out1 == out2
# --- Negative input ----------------------------------------------------------
def test_negative_max_passes_raises():
"""Negative N is a CLI input error; helper rejects it explicitly."""
src = [_mk_cluster("x", 4)]
with pytest.raises(ValueError):
split_oversized_clusters(src, max_passes=-1)
if __name__ == "__main__":
import sys
raise SystemExit(pytest.main([__file__, *sys.argv[1:]]))