Skip to content

Keep the memory limit exact instead of amortizing it in tight loops#2695

Merged
lahma merged 1 commit into
sebastienros:mainfrom
lahma:keep-memory-limit-exact
Jul 15, 2026
Merged

Keep the memory limit exact instead of amortizing it in tight loops#2695
lahma merged 1 commit into
sebastienros:mainfrom
lahma:keep-memory-limit-exact

Conversation

@lahma

@lahma lahma commented Jul 15, 2026

Copy link
Copy Markdown
Collaborator

Problem

The tight-loop constraint amortization (#2672) checks the built-in Time, Cancellation and MemoryLimit constraints once every 64 statements. That reasoning is sound for a clock or a cancellation token — a check reads external state it does not consume, so bounded detection latency is equivalent to per-statement checking. But allocation is irreversible and unbounded per statement: a single iteration can allocate arbitrarily much (e.g. s += s doubling), so a function-body tight loop can overshoot the configured LimitMemory cap — up to a real OutOfMemoryException — before the next 64-statement check fires.

Because #2688 extended the tight lane to while/do-while and #2672 keeps it armed under constraints, an engine that relies on LimitMemory to sandbox untrusted code now enforces a much coarser bound than in 4.12.0, which had no while/do-while tight lane and checked memory every statement.

Fix

Move MemoryLimitConstraint into the exact partition so memory-limited engines check it per statement again (as before the tight lane existed), preserving the limit as a hard-ish bound. Time and Cancellation stay amortized, so the common case — a timeout-only or unconstrained engine — keeps the tight-lane fast path completely unchanged. Only engines that register LimitMemory are affected, and they simply revert to pre-#2672 behavior.

Testing

Full Jint.Tests green (3603/3540, net10/net472), including the existing ShouldThrowMemoryLimitExceededInsideFunctionLocalTightLoop / timeout tight-lane tests — the memory limit still fires, now per statement.

🤖 Generated with Claude Code

https://claude.ai/code/session_01XM8rSnn8j66kDCTaP2exNK

The tight-loop constraint amortization (sebastienros#2672) checks the Time, Cancellation
and MemoryLimit constraints once every 64 statements. That is sound for a clock
or a cancellation token - a check reads external state it does not consume - but
allocation is irreversible and unbounded per statement: a single iteration can
allocate arbitrarily much (e.g. `s += s` doubling), so a function-body tight
loop can overshoot the configured cap, up to a real OutOfMemoryException, before
the next 64-statement check fires. v4.12.0 had no while/do-while tight lane and
checked memory every statement.

Move MemoryLimitConstraint into the exact set so memory-limited engines check it
per statement again (as before the tight lane), preserving the limit as a
hard-ish bound for sandboxing untrusted code. Time and cancellation stay
amortized, so the common timeout-only / unconstrained engine keeps the tight-lane
fast path unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XM8rSnn8j66kDCTaP2exNK
@lahma
lahma enabled auto-merge (squash) July 15, 2026 08:47
@lahma
lahma merged commit f5636c9 into sebastienros:main Jul 15, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant