ReviewOS

zig-utils/zig-js

Runtime: root caller realms in native wrappers across moving callbacks

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

Discovered while isolating #827.

A managed child-realm function that calls a foreign native and triggers moving collection inside nested user code exposes raw caller-realm restores. The direct Array.from case is fixed under #827, but the same exact operation still reproduces through adjacent wrappers:

  • Function.prototype.call restores the raw saved_env in funcProtoMethod after the nested moving callback, so the child function resumes in a stale/wrong environment and its next global lookup throws ReferenceError.
  • $262.evalScript saves raw env, this_value, and global_object; a moving callback during the evaluated script can leave those restore values stale. This currently reaches a managed-cell write-barrier assertion with a cross-realm constructor witness.

Audit scope should include every synchronous native wrapper that enters another defining realm and later restores raw interpreter realm state. Use the existing precise environment/value roots and reload on restore; do not disable moving GC, pin cells, or add lifecycle machinery.

Acceptance:

  • Deterministic forced tree-walker and required-bytecode moving witnesses for direct invocation, Function.prototype.call/apply, and realm evalScript.
  • Correct abrupt-completion restoration as well as normal return.
  • Unsuppressed TSan and allocator-clean focused coverage.
  • Full native suite and nonzero relevant test262 census before landing.

Related: #827 and parent #465.

9 days ago

Landed 556f25f6 — fix(gc): root native realm restores across moving callbacks (#830) directly on main as Chris, without trailers. Built-in defining-realm pointers are now explicit strong GC edges and caller interpreter state is restored from precise roots after moving callbacks on both normal and abrupt paths. Evidence: focused forced-moving tree-walker/required-bytecode matrix passes with zero leaks; unsuppressed TSan passes in 421.7s; Function.prototype.call 49/49, apply 48/48, and eval 10/10 in the pinned test262 corpus; combined units 2214 pass / 1 platform skip / 0 fail / 0 leak. #831 remains separate: its retained-native/dropped-global teardown witness still needs independent leak/finalization proof.

Sign in to comment on this issue.