ReviewOS

zig-utils/zig-js

Arrays: searches must see String exotic prototype characters

#1070
Closed chris opened this 8 days ago ·
8 days ago

Parent: #498 Related: #1069

Executed against Node and the 1be5308 before binary in both tree and required-VM modes. With values = new Array(3) and Object.setPrototypeOf(values, new String("abc")), borrowed Array indexOf("b") and lastIndexOf("b") return -1 rather than 1. includes("b") also wrongly returns false, while includes(undefined) wrongly returns true when all three positions are inherited String characters. The same result occurs at length 1,000,000.

Executed search matrix (length, indexOf, lastIndexOf, includes("b"), includes(undefined)):

Node: [[3,1,1,true,false],[1000000,1,1,true,true]] zig-js tree and required VM, including the #1069 candidate: [[3,-1,-1,false,true],[1000000,-1,-1,false,true]]

The prototype-presence gate selects sparse own-index scanning because it does not classify virtual String character descriptors as inherited indexed properties. Read/write eligibility should share the complete classification rather than diverge.

Acceptance:

  • IndexOf/lastIndexOf/includes search inherited virtual String characters with exact HasProperty/Get ordering and correct missing-vs-inherited-undefined behavior.
  • Share String-aware prototype classification across indexed read/write eligibility.
  • Pin short/long/deep-prototype, fromIndex, UTF-16, getters/Proxy effects, mutation, no-GIL, TSan, and ReleaseSafe witnesses.
  • Long observable traversals poll pending termination and watchdog requests without changing logical AST/bytecode step accounting.
  • Exact before/after affected Array corpus and tree/required-VM outputs have no new failures/divergences.

The large-length sparse-path guard must also be audited by execution before this issue closes; do not assume the scan-size threshold is semantically valid. No performance publication is intended.

The guard audit also reproduced a separate threshold failure: an Array of length 5,000,000 with ordinary prototype { 1: "b" } gives Node [1,1] for indexOf/lastIndexOf, but zig-js gives [-1,-1] in both tiers. The scan-size cap cannot authorize skipping inherited properties.

The receiver-side audit is executed too: a getter at own index 0 that creates index 1 makes indexOf return -1 and includes return false where Node returns 1 and true. The descending lastIndexOf witness correctly expects -1 because creation occurs after index 1 was already visited. Eligibility must therefore refuse observable receiver reads as well as inherited reads. Mapped arguments stale reads are a separate shared-helper prerequisite in #1071.

8 days ago

The requested large-length guard audit is now executed. At new Array(5000000) with ordinary prototype { 1: "b" }, Node indexOf/lastIndexOf both return 1, while both zig-js tiers return -1. This is independent of virtual String descriptors: the ilen <= (1 << 22) condition bypasses the inherited-property path for any long Array. The before witness is preserved locally as zig-js-large-prototype-search-probe.js. This cap must not be retained as a semantic shortcut.

8 days ago

Landed in 9928b46. Search eligibility now shares String-aware prototype classification with indexed writes. Plain Array data retains dense/sparse scanning; observable receivers and ancestors use ordered HasProperty/Get or Get traversal. The invalid scan-length cap is removed. Native searches poll stop/watchdog requests between complete lookups without changing logical step accounting.

Validation:

  • original inherited String, getter-mutation, and five-million-length witnesses now exactly match Node in tree and required-VM modes;
  • focused search tests pass 4/4 in normal, unsuppressed TSan, and ReleaseSafe, including four actual no-GIL workers and six stop/watchdog cases armed during fromIndex coercion;
  • prior String write/vector eligibility tests pass 4/4 normally and under unsuppressed TSan;
  • Arguments 263 and Array 3,081 corpus cases and paired VM witnesses are byte-identical before/after, all passing with zero divergences: flips 0 test262 cases, zero measured regressions in the 3,344-case scope;
  • after rebasing onto cce66bb, focused normal/TSan/ReleaseSafe checks again pass 4/4.

The actual-parent cce66bb → 9928b46 comparison also passes with byte-identical output: indexOf 201, lastIndexOf 198, and includes 30 cases, all passing and agreeing in both tiers (429 cases, zero failures/divergences). No throughput or public conformance-score claim is made. Full hosted CI will remain separate.

Sign in to comment on this issue.