ReviewOS

zig-utils/zig-js

For-in: honor Proxy prototypes and avoid has-trap enumeration

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

Parent: #467. Related: #1106.

Executed on pinned 4c1f31af (ReleaseSafe/Darwin), Node v26.10.0, and system JavaScriptCore. These are existing language-semantic differences, separate from native enumeration transport/recovery.

A Proxy reporting { inherited: 2 } through getPrototypeOf over { own: 1 } yields [["own","inherited"],true] on Node/JSC, where the boolean records that the trap ran. zig-js yields [["own"],false].

var prototype = { inherited: 2 }, calls = 0;
var subject = new Proxy({ own: 1 }, {
  getPrototypeOf() { calls++; return prototype; }
});
var keys = [];
for (var key in subject) keys.push(key);
JSON.stringify([keys, calls > 0]);

A throwing has trap is not invoked by Node/JSC enumeration. zig-js invokes it and loses the own key:

var trace = [];
var subject = new Proxy({ own: 1 }, {
  has() { trace.push("has"); throw "unexpected has"; },
  getOwnPropertyDescriptor(target, key) {
    trace.push("desc:" + key);
    return Reflect.getOwnPropertyDescriptor(target, key);
  }
});
var keys = [], thrown = null;
try { for (var key in subject) keys.push(key); } catch (error) { thrown = error; }
JSON.stringify([keys, trace, thrown]);

Node/JSC: [["own"],["desc:own"],null]. zig-js: [[],["desc:own","has"],"unexpected has"].

ECMA-262 EnumerateObjectProperties requires prototype enumeration and obtains property attributes through [[GetOwnProperty]]. Proxy enumeration is exempt from matching the illustrative iterator algorithm exactly, so use observable results and both engine oracles rather than assuming identical implementation order.

Acceptance:

  • Pin both executed witnesses in tree-walker, required-bytecode, optimizer-off/on, serialized/no-GIL controls.
  • Honor reported prototypes and preserve shadowing, deletion, trap exceptions, and precise moving roots.
  • Replace language-level has-trap exception/GC witnesses with appropriate descriptor/prototype callbacks; retain exact callback/no-replay recovery coverage.
  • Measure exact-parent affected test262 path sets and run the unchanged thread/fuzz gates; no exclusions or relaxed budgets.
9 days ago

Landed and pushed on main in4b2ec38c (rooted Proxy prototype callbacks) and3100f072 (for-in descriptor/prototype semantics and tests).

Enumeration now honors Proxy-reported prototypes, evaluates descriptors when candidates are reached, preserves callback/body ordering, deletion and non-enumerable shadowing, and never invokes a Proxy has trap. Tree-walker, bytecode and native candidate dispatch share the same semantic operation. Captured target/handler/reported prototype and loop receivers remain rooted through reentry and actual relocation.

Acceptance evidence:

  • Both original executed witnesses and ordering/deletion/shadowing/throw/invariant/revocation controls pass across8 tree/required-bytecode, optimizer off/on and serialized/no-GIL configurations; Node and JavaScriptCore agree.
  • Existing native exception/catch-recovery, exact budget and moving-root witnesses now use descriptor callbacks, retaining exact one-call/no-replay and relocation assertions.
  • Affected optimizer/for-in cluster276/276 in Debug, ReleaseSafe and unsuppressed ReleaseSafe+TSan, no skips; instrumentation verified and no supplied suppressions.
  • Exact parent89fec617 comparison:18 test262 subtrees,8339 cases per execution mode;0 failures both sides, identical failing paths,0 flips. Five additional standalone oracle probes pass.
  • Default400 and verify300 fuzz pass. Corrected #1109 full mid-GC900/900 and TSan60/60 pass with unchanged watchdogs. The original2 finite-wait failures are preserved in #1109's diagnosis, not discarded.
  • Full no-GIL Debug and ReleaseSafe each253/253 under the unchanged600-second per-case limit.

These local acceptance gates pass. Hosted full-unit integration is still incomplete: run38180234010 has49 successful checks and4 cancelled shards; its single TSan shard rerun stalls after313/699 completed cases and reaches the unchanged60-minute job limit. Evidence is on #494; no full-green claim is made. The local combined unit relink remains #933's explicit memory-cap block. New push CI is tracked separately. Class support is the next implementation boundary.

Sign in to comment on this issue.