Both rows that could not go red are decided, each by measurement.
test_lexicon.py::test_redos_pathological_subagent_input_returns_fast is REVIVED,
not retired. The row was not dead because the seed form is safe -- it was dead
because both earlier payloads made the prefix match at ONE start position, and
the cost is per-prefix-match. Repeating `spawn an agent that ` instead makes it
match K times, each driving its own O(N) lazy scan for a keyword never supplied:
K x O(N) against the seed's `(?:.*?\s+)?`, K x O(1) against the shipped
`{0,12}?` bound. Measured through scan_lexicon at 1500/3000/6000/12000 words:
seed 0.091/0.283/1.085/4.091s (exponent 1.92), shipped 0.047/0.051/0.094/0.190s
(exponent 1.01). Verified red with the seed form patched in: 4.21s against the
2.0s bound. The nesting the old comment blamed was a red herring.
test_output.py::test_pathological_input_returns_within_a_bound moves to CPU time
with a 20.0s bound, and the "or a hang" half of its claim is retired. The wall
clock was kept because a blocking hang burns no CPU -- true in general, and
inapplicable to a path with no open(), socket, subprocess, thread, lock or sleep
anywhere on it. Same payload, idle vs ~4x oversubscription: wall 3.30 -> 21.63s
(2x over the old 10.0s bound), cpu 3.30 -> 7.62s. It guarded a mode it could not
have while paying the full false-red premium. No in-repo vulnerable form can
turn this row red, so the bound was proved live against what it actually guards
-- a future pattern quadratic on long runs, `A+\s*EXFILTRATE` -- which failed it
at 64.77s CPU, 3.2x over.
redos_clock.py and the clock's pin test both documented this row as the
deliberate wall-clock holdout; both corrected.
792 passed, 6/6 documented gaps hold.
38 lines
2 KiB
Python
38 lines
2 KiB
Python
"""The one clock every ReDoS bound in this suite is measured against.
|
|
|
|
Process CPU time, not wall clock: a ReDoS blowup is spent cycles, and a loaded
|
|
machine steals wall clock without adding any. In 0.7.0 these bounds ran on
|
|
``time.monotonic()`` and two of them failed at 2.24s / 3.66s against a 2.0s
|
|
bound while two census processes had the CPU; the same rows passed 3/3 on an
|
|
idle machine. The scans had not slowed down — they were descheduled.
|
|
|
|
This lives in its own module, imported by all six test files, rather than being
|
|
copied into each. The suite already holds that rule for the code it measures
|
|
("never re-implement a predicate you measure — import it"), and it binds harder
|
|
here: ``test_output.py::test_the_redos_clock_ignores_time_this_process_did_not_spend``
|
|
pins ONE implementation. Five copies would leave four of them unpinned and free
|
|
to drift back to a wall clock without a single test going red.
|
|
|
|
What this clock gives up: a scan that BLOCKS forever burns no CPU, so it would
|
|
hang the suite instead of failing it. Acceptable for every caller here — these
|
|
scanners are pure regex over an in-memory string, with no I/O and no locks, so
|
|
the only way they can be slow is by spending cycles. That is not a concession
|
|
made grudgingly per row: ``test_pathological_input_returns_within_a_bound`` was
|
|
the last holdout, kept on a wall clock precisely to catch a blocking hang, and
|
|
it was retired once the path was checked for anything that could block and
|
|
found to contain none. A wall clock that guards an impossible mode still
|
|
charges the full false-red premium — measured there at 21.6s against a 10.0s
|
|
bound under load, on a scan that spent 7.6s.
|
|
"""
|
|
import time
|
|
|
|
|
|
def scan_seconds(scanner, payload) -> float:
|
|
"""CPU seconds ``scanner(payload)`` cost.
|
|
|
|
Pinned by ``test_the_redos_clock_ignores_time_this_process_did_not_spend``
|
|
in ``test_output.py``, which carries the measurements behind the choice.
|
|
"""
|
|
start = time.process_time()
|
|
scanner(payload)
|
|
return time.process_time() - start
|