ReviewOS

zig-utils/zig-js

GC: relocate tree-walker catch scopes across moving try completions

#832
Closed chris opened this 9 days ago ·
9 days ago

Discovered while strengthening #830's abrupt-completion moving witness.

A fresh tree-walker function enters try, a nested native/JIT callback triggers the deterministic moving nursery safepoint, and the completion enters catch. The catch body's first binding lookup can spin indefinitely. A 3-second sample is entirely in evalTry -> evalCatchClause -> lookupIdentValue, with the Environment hash lookup walking corrupted/cyclic scope state.

Source audit matches the runtime symptom: evalCatchClause and adjacent evalTry state retain raw self.env pointers across evaluation that can run user code and move managed Environment cells. This is independent of #830's foreign-native realm restore; #830's production root is present when the catch lookup still spins.

Scope:

  • Reduce to direct try/throw/catch/finally witnesses with the actual moving safepoint, without a foreign realm.
  • Precisely root and reload every retained tree-walker Environment/Value used across try, catch binding initialization, catch body, and finally.
  • Preserve completion precedence, exception identity, destructuring/default/computed catch bindings, lexical shadowing, labeled break/continue/return, disposal, debugger/watchdog state, and OOM cleanup.
  • Mirror or prove the VM handler path independently; do not broaden bytecode fallback.

No-workaround rules:

  • No moving-GC disable, pinning, timeout/suppression, catch-source recognition, environment-chain repair after corruption, or forced lower-tier fallback.
  • Do not fold native-wrapper, generator ownership, Promise/job, Worker, or unrelated lifecycle changes into this fix.

Acceptance:

  • Deterministic forced tree-walker and required-bytecode normal/throw/return/break/continue/finally witnesses prove actual movement and bounded completion.
  • Exact pre-fix witness fails or times out for the diagnosed scope reason; candidate finishes with the expected value and no leak.
  • Catch destructuring/default/computed-key throws and nested/reentrant try/finally preserve exact ordering.
  • Unsuppressed TSan, focused moving-GC stress, full units, threadfuzz, and nonzero relevant test262 statement/catch/finally census pass.

Related: #830, #465, #791.

9 days ago

Landed 215a3506 — fix(gc): relocate try completions and catch scopes (#832) directly on main as Chris, without trailers. Tree-walker try/catch/finally state now roots and reloads environments, exceptions, completion values, held throws, and return values across moving callbacks while preserving completion precedence. Evidence: 10 forced-moving executions across tree-walker and required-bytecode modes; unsuppressed TSan pass in 91.7s; pinned test262 try statements 201/201; threadfuzz 400/400 default and 300/300 verifier; combined units 2214 pass / 1 platform skip / 0 fail / 0 leak.

Sign in to comment on this issue.