ReviewOS

zig-utils/zig-js

Errors: Proxy and detached-buffer messages are V8-derived, not JavaScriptCore's

#903
Closed chris opened this 11 days ago ·
11 days ago

Parent: #459

Problem

zig-js's Proxy and detached-buffer message tables read as V8-derived, not JavaScriptCore-derived. Of 15 Proxy cases probed, 14 diverge; the only one that already matches is new Proxy({}, null).

This is the same root cause as the JSON.stringify cyclic message fixed in 8ba5966, which was emitting V8's Converting circular structure to JSON. Worth treating as a class: wherever a message was written from memory rather than from a JSC differential, it is likely V8's.

Every one of the 80 cases in this sweep threw in both engines, so the semantics are right and this is purely message text — with one exception noted below.

Proxy invariant traps (11 cases)

zig-js uses a lowercase uniform prefix; JSC's prefix varies per trap, so this needs a per-trap table rather than one rule:

zig-js:  proxy 'get' must report ...
JSC:     Proxy handler's 'get' result of ...
JSC:     Proxy's 'defineProperty' trap returned true even though ...
JSC:     Proxy object's 'isExtensible' trap returned true when ...

JSC also interpolates the offending property name into the ownKeys omission message (... the non-configurable property 'k' that was not in the result ...); zig-js does not.

Revoked proxy (3 cases) — a merge, not a split

JSC emits ONE string for every operation:

Proxy has already been revoked. No more operations are allowed to be performed on it

zig-js parameterizes by trap (Cannot perform 'get'/'set'/'apply' on a proxy that has been revoked). Collapsing to the single constant fixes all of them at once.

Detached buffers are over-partitioned (8 cases)

JSC uses exactly three strings across the whole surface:

situationJavaScriptCore
any operation on an existing view — TypedArray methods and DataView accessors alikeUnderlying ArrayBuffer has been detached from the view or out-of-bounds
constructing a view over a detached bufferBuffer is already detached
ArrayBuffer.prototype.sliceReceiver is detached

zig-js has at least four and splits TypedArray from DataView where JSC does not.

One real bug in that cluster

dv.subarray(0) on a detached view emits zig-js's construction message (Cannot construct a TypedArray on a detached buffer) rather than its view-operation message, because subarray is routed through the construction path and leaks that wording. .sort / .slice / .fill correctly use the view message. That one is worth fixing independently of the JSC alignment.

Verification

scratchpad/sweep/probe.sh diffs against a real JavaScriptCore backend. Drive probes from inside a function — zig-js --eval runs script code, so an indirect eval shares global scope with the harness.

11 days ago

Landed in 7ba86e4. A 99-case differential against a real JavaScriptCore backend goes 25/99 → 96/99.

setbeforeafter
Proxy invariants2/2525/25
Proxy traps (callability, return shapes)7/2121/21
detached views6/2726/27
ArrayBuffer.prototype methods7/1917/19
Atomics3/77/7

The issue described this as "a pure string-table swap with no semantic work". That was right about the Proxy half and wrong about the buffer half — three zig-js strings each covered two JavaScriptCore messages, so a global replace would have silently mis-worded them:

  • ArrayBuffer is detached (18 sites) → Receiver is detached for an ArrayBuffer.prototype method, Buffer is already detached when constructing a view. Classified by enclosing function.
  • invalid typed array offset → misaligned byteOffset vs an offset past the end.
  • Cannot construct a TypedArray on a detached buffer → construction vs the subarray path.

Generating the mapping from probe output rather than transcribing it is what surfaced those; worth doing again for #901/#904.

The subarray finding, revised

The issue called this "a genuine bug: subarray is routed through the construction path and leaks that wording". The routing is real, but it is not a bug — per spec subarray throws through TypedArraySpeciesCreate → the TypedArray constructor, so the construction wording is arguably the more accurate one. JSC simply uses its generic view message there.

I verified that argument-coercion order (3/3) and @@species getter/constructor behaviour (2/2) already match JSC exactly, so nothing observable is wrong. Changing only the message would mean special-casing the constructor's wording for one caller — a workaround — so a.subarray(0) on a detached view is left reporting Buffer is already detached. That is the single remaining genuine divergence.

Two extrapolations, flagged

JavaScriptCore implements neither ArrayBuffer.prototype.transferToImmutable nor sliceToImmutable, so there is no oracle for them. They follow their measured siblings and report Receiver is detached.

Two non-divergences

structuredClone and SharedArrayBuffer are absent from home-tool's JavaScriptCore, so those probe rows are harness artifacts rather than differences.

--diag: 3,820 pass / 0 fail across built-ins/{Proxy 311, Reflect 153, ArrayBuffer 221, DataView 561, Atomics 390, TypedArray 1446, TypedArrayConstructors 738}.

11 days ago

Follow-up landed in 656f4d81 — tools(threads): reconcile detached-buffer disposition evidence (#903). The message change in 7ba86e4 left the PR-249 terminal reference expectation stale. The case still fails with the intended detached-buffer TypeError; both current mode expectations now match the measured exact message. Historical transcripts are preserved.

Validation: the old expectation fails 4/5 and the improved audit names the case, missing message, and observed output. Updated reference audit/probes pass 5/5; all six terminal cases match in both GIL/no-GIL modes (12/12) within the unchanged 20-second budget. The no-GIL corpus gate self-test passes. No engine semantics or terminal classification changed.

Sign in to comment on this issue.