Skip to content

Loops nested deeper than MAX_LOOP_DEPTH (32): break silently targets the 32nd loop's exit and corrupts iteration state #336

Description

@InauguralPhysicist

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions