You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(reg-vm): recursive native dispatch must honor step_budget/cancel gate
The recursive native fast paths in the CallKnown handler (self-recursive
run_jit_self_recursive_int and mutual-recursive try_native_mutual_recursive_int)
were gated only on mem_budget.is_none(), not step_budget or cancel. Cranelift
code polls neither the step budget nor the cancel flag, so a recursive native
candidate (fib, is_even/is_odd) could run to completion and silently bypass
preemption that the host armed -- exactly what the normal native-tier gate in
try_native already refuses for (it checks all three: step_budget, cancel,
mem_budget). This is a sandbox-soundness gap: a watchdog/step limit would not
stop native recursion.
Fix: factor a single `RegVm::native_limits_unarmed()` (step_budget.is_none() &&
cancel.is_none() && mem_budget.is_none()) and gate both recursive hooks on it;
dedup the try_native gate to `!native_limits_unarmed()` so there is one source of
truth. With any limit armed, recursion runs on the interpreter / tier-0 executor,
which tick()s every instruction (and tick() polls both step_budget and cancel).
Test plumbing: `eval_main_with_args_native_with_limits` threads VmLimits into the
native eval path. Regressions (jit_acceptance):
- native_self_recursion_refused_when_step_budget_armed (native_calls == 0)
- native_mutual_recursion_refused_when_step_budget_armed (native_calls == 0)
- native_recursion_refused_when_cancel_armed (present, un-triggered flag)
- native_self_recursion_step_budget_preempts (small budget actually trips)
Verified: jit_acceptance 92/0, differential 33/0, vm-jit 83/0, hostile 16/0,
static 627/0, default build clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
0 commit comments