Found in an adversarial language review at 95a0d76. Silent wrong behavior, rc=0, JIT-independent.
Root cause
The for/loop compile paths only push a LoopCtx while c->loop_depth < MAX_LOOP_DEPTH (src/compiler.c:1490, 1631; cap = 32 at compiler.c:15) and otherwise leave lp = NULL. A break inside loop 33+ still sees c->loop_depth > 0 and registers its jump with c->loops[c->loop_depth - 1] — the 32nd loop's context — so it gets patched to the 32nd loop's exit. It also emits (at most) one OP_LOOP_ENV_END while jumping across many live loop envs and leaves the inner iterator states on the VM stack.
Repro
33 nested for loops, outermost range of 2, inner ones range of 1, innermost body prints body then break; markers after33 (right after loop 33) and after32 (after loop 32):
Expected: body ×2, after33 ×2, after32 ×2, done ×1.
Actual: body ×1, after33 ×0, after32 ×1, done ×1 — the break skips after33 entirely and the outermost loop loses its second iteration (stack/iterator corruption). rc=0 throughout.
(Repro generator: 33-deep loop nest; happy to attach the 10-line python emitter — it's deterministic.)
Fix
Exceeding MAX_LOOP_DEPTH should be a hard compile error (or the array should grow). A break/continue must never bind to a context that isn't its enclosing loop.
Test
A 33-deep nest with an innermost break behaves identically to a 32-deep one (wrong output before the fix); plus a compile-error test if the cap becomes diagnosed.
Found in an adversarial language review at 95a0d76. Silent wrong behavior, rc=0, JIT-independent.
Root cause
The
for/loopcompile paths only push aLoopCtxwhilec->loop_depth < MAX_LOOP_DEPTH(src/compiler.c:1490, 1631; cap = 32 at compiler.c:15) and otherwise leavelp = NULL. Abreakinside loop 33+ still seesc->loop_depth > 0and registers its jump withc->loops[c->loop_depth - 1]— the 32nd loop's context — so it gets patched to the 32nd loop's exit. It also emits (at most) oneOP_LOOP_ENV_ENDwhile jumping across many live loop envs and leaves the inner iterator states on the VM stack.Repro
33 nested
forloops, outermostrange of 2, inner onesrange of 1, innermost body printsbodythenbreak; markersafter33(right after loop 33) andafter32(after loop 32):Expected:
body×2,after33×2,after32×2,done×1.Actual:
body×1,after33×0,after32×1,done×1 — the break skipsafter33entirely and the outermost loop loses its second iteration (stack/iterator corruption). rc=0 throughout.(Repro generator: 33-deep loop nest; happy to attach the 10-line python emitter — it's deterministic.)
Fix
Exceeding
MAX_LOOP_DEPTHshould be a hard compile error (or the array should grow). Abreak/continuemust never bind to a context that isn't its enclosing loop.Test
A 33-deep nest with an innermost break behaves identically to a 32-deep one (wrong output before the fix); plus a compile-error test if the cap becomes diagnosed.