Skip to content

fix(array): respect deletion of Array.prototype[Symbol.iterator]#462

Merged
dowdiness merged 2 commits into
mainfrom
worktree-typeerror-retriage
Jun 25, 2026
Merged

fix(array): respect deletion of Array.prototype[Symbol.iterator]#462
dowdiness merged 2 commits into
mainfrom
worktree-typeerror-retriage

Conversation

@dowdiness

@dowdiness dowdiness commented Jun 25, 2026

Copy link
Copy Markdown
Owner

Summary

Two complementary fixes to get_computed_property_impl's Array symbol lookup arm in interpreter/runtime/property.mbt.

Fix 1 — Remove synthesis block so delete Array.prototype[Symbol.iterator] is respected:

The Array arm had a hard-coded block that synthesized a default array iterator after the prototype chain walk found nothing. This made delete Array.prototype[Symbol.iterator] a no-op: arrays remained iterable, so ArrayBindingPattern destructuring (§7.4.1 GetIterator) never threw the required TypeError. setup_iterator_protocol already writes symbol_properties[iterator_sym.id] on Array.prototype (builtins_iterator.mbt:60), so the chain walk handles the normal case without this fallback.

Fix 2 — Continue lookup through non-Object prototype overrides (Object.setPrototypeOf(arr, [])):

The _ => () wildcard following the match get_array_prototype(...) silently dropped Array(...), Proxy(...), and other walkable prototype values. Replaced with Null | Undefined => () (end of chain) and a proto_val => get_computed_property(proto_val, ...) delegate that routes through the existing full-dispatch path. This fixes:

  • Object.setPrototypeOf(a, []); for (const x of a) {}
  • Object.setPrototypeOf(a, new Proxy(Array.prototype, {})); a[Symbol.iterator]

The delete fix is preserved: after delete Array.prototype[Symbol.iterator], the chain walk finds nothing in the Object(proto_data) branch and falls through to Null | Undefined => (), returning Undefined as GetIterator requires.

Lookup order after these fixes

1. own symbol descriptor (accessor getter)     [906-912]  ← instance getter override
2. own symbol property bag                     [914-918]  ← instance value override
3. match get_array_prototype(realm_state, arr):
   Object(proto_data) → inline walk           [924-960]  ← normal case (Object.prototype chain)
   Null | Undefined   → ()                    [962]      ← null prototype / end of chain
   proto_val          → get_computed_property [963]      ← Array/Proxy/Map/etc. override
4. return Undefined                            [964]

Test Results

  • ary-init-iter-get-err-array-prototype filter: 80/80 ✓ (was 0/80)
  • language/statements/for-of: 1358/1414, zero new regressions vs CI baseline
  • language/expressions/array: no new regressions vs CI baseline
  • Full unit test suite: 2219/2219 ✓

Fixes 40 strict + 40 non-strict test262 failures across arrow-function/dstr, generators/dstr, class/dstr, function/dstr, and ~12 other destructuring families.

Test plan

  • moon test 2219/2219
  • make test262-filter FILTER=ary-init-iter-get-err-array-prototype 80/80
  • make test262-filter FILTER=language/statements/for-of zero new regressions vs CI baseline
  • make test262-filter FILTER=language/expressions/array zero new regressions vs CI baseline

🤖 Generated with Claude Code

The `get_computed_property_impl` Array arm had a hard-coded synthesis
block that returned a default array iterator for Symbol.iterator lookups
after the prototype chain walk found nothing.  This meant that
`delete Array.prototype[Symbol.iterator]` had no effect: arrays remained
iterable, so ArrayBindingPattern destructuring (§7.4.1 GetIterator) never
threw the expected TypeError.

The block was described as a fallback for "before setup_iterator_protocol
is called", but setup_iterator_protocol writes symbol_properties[iterator_sym.id]
on Array.prototype (builtins_iterator.mbt:60), so the prototype chain walk
at lines 935-936 handles all normal cases and the synthesis block is
unreachable in that path — it only fires in the delete case, which is the
bug.

The instance-level checks at lines 906-918 already cover own-property
Symbol.iterator overrides (accessors and values), so removing the synthesis
block does not regress any custom-iterator use case.

Fixes 40 strict + 40 non-strict test262 failures
(language/.../dstr/ary-init-iter-get-err-array-prototype across
arrow-function, generators, class methods, function params, etc.)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 25, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@dowdiness, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 3 minutes and 42 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c2879418-57ca-42a4-8001-998bddcb45ee

📥 Commits

Reviewing files that changed from the base of the PR and between a4d48b8 and c1b97c1.

📒 Files selected for processing (1)
  • interpreter/runtime/property.mbt
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch worktree-typeerror-retriage

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c6cacec520

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

},
)
}
return Undefined

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Continue symbol lookup through array prototype overrides

When an array's prototype override is another array or a Proxy, get_array_prototype returns Array(...)/Proxy(...), so this return Undefined is now reached because the preceding walk only handles Object(proto_data). After deleting the fallback, cases like Object.setPrototypeOf(a, []); a[Symbol.iterator] or Object.setPrototypeOf(a, new Proxy(Array.prototype, {})); for (const x of a) {} lose the inherited Array.prototype iterator and throw, even though OrdinaryGet should keep walking through those prototype objects; route this path through the existing symbol-prototype walker or handle non-Object prototypes before returning.

Useful? React with 👍 / 👎.

@github-actions

github-actions Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Benchmark Results

Run: https://github.com/dowdiness/js_engine/actions/runs/28181387137

startup/tiny_program is the PR #153 / issue #141 guardrail for built-in realm-stamping startup cost.

Stage summary

stage benchmarks total mean slowest benchmark slowest mean noisy rows
startup 3 2.516 ms startup/phase/new_interpreter 1.272 ms 1
frontend 7 0.955 ms pipeline/parse_heavy 0.542 ms 2
execution 26 16641.074 ms exec/fibonacci_30 15130.839 ms 2

Focused bytecode base-vs-head comparison

Base-vs-head deltas are reporting-only. Negative delta and PR/base < 1.00x mean the PR is faster; interpret high-CV or noisy rows cautiously.

benchmark stage base mean PR mean delta PR/base base CV PR CV noisy
baseline/bytecode/closure_factory execution 16.230 ms 16.048 ms -1.1% 0.99x 6.2% 6.9% no
pipeline/bytecode/evaluate execution 10.376 ms 11.388 ms +9.8% 1.10x 2.1% 1.4% no
isolate/bytecode/call_frame execution 7.770 ms 8.153 ms +4.9% 1.05x 4.6% 2.0% no
isolate/bytecode/runtime_helpers execution 11.125 ms 11.952 ms +7.4% 1.07x 2.5% 0.9% no
isolate/bytecode/local_access execution 38.853 ms 37.570 ms -3.3% 0.97x 0.8% 3.6% no
isolate/bytecode/env_access execution 38.919 ms 38.622 ms -0.8% 0.99x 0.5% 0.9% no
isolate/bytecode/captured_access execution 39.654 ms 38.164 ms -3.8% 0.96x 1.5% 1.4% no
isolate/bytecode/dispatch_stack execution 24.799 ms 24.569 ms -0.9% 0.99x 0.7% 2.7% no

Base-vs-head comparison

benchmark stage base mean PR mean delta PR/base base CV PR CV noisy
startup/tiny_program startup 1.318 ms 1.244 ms -5.6% 0.94x 7.3% 4.1% no
lexer/small frontend 0.036 ms 0.035 ms -4.9% 0.95x 51.8% 27.3% base, PR
lexer/large frontend 0.300 ms 0.294 ms -2.0% 0.98x 0.9% 0.8% no
exec/fibonacci_30 execution 15122.945 ms 15130.839 ms +0.1% 1.00x 1.6% 1.7% no
exec/property_chain execution 15.960 ms 16.110 ms +0.9% 1.01x 14.7% 10.6% no
startup/phase/parse_tiny frontend 0.002 ms 0.002 ms +2.6% 1.03x 0.8% 0.6% no
startup/phase/new_interpreter startup 1.198 ms 1.272 ms +6.2% 1.06x 10.3% 23.1% PR
startup/phase/execute_preparsed_tiny execution 0.001 ms 0.001 ms +0.0% 1.00x 1.2% 1.0% no
startup/phase/event_loop_drain_empty startup 0.000 ms 0.000 ms -0.5% 0.99x 1.3% 1.2% no
startup/phase/result_stringify_output execution 0.000 ms 0.000 ms -1.1% 0.99x 0.6% 0.6% no
exec/array_map_filter execution 21.901 ms 21.602 ms -1.4% 0.99x 18.5% 18.0% base, PR
exec/closure_factory execution 34.277 ms 33.621 ms -1.9% 0.98x 5.5% 7.5% no
baseline/closure_legacy/closure_factory execution 33.334 ms 31.559 ms -5.3% 0.95x 7.9% 9.4% no
baseline/bytecode/closure_factory execution 16.230 ms 16.048 ms -1.1% 0.99x 6.2% 6.9% no
isolate/bytecode/dispatch_stack execution 24.799 ms 24.569 ms -0.9% 0.99x 0.7% 2.7% no
isolate/bytecode/local_access execution 38.853 ms 37.570 ms -3.3% 0.97x 0.8% 3.6% no
isolate/bytecode/env_access execution 38.919 ms 38.622 ms -0.8% 0.99x 0.5% 0.9% no
isolate/bytecode/captured_access execution 39.654 ms 38.164 ms -3.8% 0.96x 1.5% 1.4% no
isolate/bytecode/call_frame execution 7.770 ms 8.153 ms +4.9% 1.05x 4.6% 2.0% no
isolate/bytecode/runtime_helpers execution 11.125 ms 11.952 ms +7.4% 1.07x 2.5% 0.9% no
isolate/bytecode/property_get execution 44.621 ms 44.805 ms +0.4% 1.00x 2.1% 2.2% no
isolate/bytecode/property_set execution 40.291 ms 40.533 ms +0.6% 1.01x 1.8% 2.3% no
isolate/bytecode/method_call execution 8.537 ms 8.674 ms +1.6% 1.02x 1.2% 1.5% no
isolate/bytecode/object_literal execution 14.020 ms 13.273 ms -5.3% 0.95x 4.5% 3.5% no
isolate/bytecode/array_literal execution 15.759 ms 15.463 ms -1.9% 0.98x 3.9% 2.9% no
exec/for_of execution 7.095 ms 6.783 ms -4.4% 0.96x 8.8% 7.3% no
exec/arithmetic_loop execution 1001.443 ms 1022.153 ms +2.1% 1.02x 1.3% 0.6% no
exec/object_construction execution 8.460 ms 8.019 ms -5.2% 0.95x 7.5% 6.0% no
exec/string_ops execution 2.202 ms 2.024 ms -8.1% 0.92x 21.3% 21.1% base, PR
pipeline/exec/lex frontend 0.032 ms 0.031 ms -2.3% 0.98x 1.6% 1.0% no
pipeline/exec/parse frontend 0.027 ms 0.027 ms -0.7% 0.99x 3.1% 3.8% no
pipeline/exec/evaluate execution 31.111 ms 29.879 ms -4.0% 0.96x 6.4% 4.3% no
pipeline/closure_legacy/evaluate execution 29.175 ms 29.273 ms +0.3% 1.00x 4.2% 5.1% no
pipeline/bytecode/compile frontend 0.023 ms 0.023 ms +0.2% 1.00x 27.4% 25.3% base, PR
pipeline/bytecode/evaluate execution 10.376 ms 11.388 ms +9.8% 1.10x 2.1% 1.4% no
pipeline/parse_heavy frontend 0.522 ms 0.542 ms +3.9% 1.04x 5.5% 7.5% no

Mean-time chart (log scale)

benchmark stage mean chart
startup/tiny_program startup 1.244 ms ##
lexer/small frontend 0.035 ms ⚠ #
lexer/large frontend 0.294 ms #
exec/fibonacci_30 execution 15130.839 ms ##############################
exec/property_chain execution 16.110 ms ########
startup/phase/parse_tiny frontend 0.002 ms #
startup/phase/new_interpreter startup 1.272 ms ⚠ ##
startup/phase/execute_preparsed_tiny execution 0.001 ms #
startup/phase/event_loop_drain_empty startup 0.000 ms #
startup/phase/result_stringify_output execution 0.000 ms #
exec/array_map_filter execution 21.602 ms ⚠ #########
exec/closure_factory execution 33.621 ms ###########
baseline/closure_legacy/closure_factory execution 31.559 ms ##########
baseline/bytecode/closure_factory execution 16.048 ms ########
isolate/bytecode/dispatch_stack execution 24.569 ms ##########
isolate/bytecode/local_access execution 37.570 ms ###########
isolate/bytecode/env_access execution 38.622 ms ###########
isolate/bytecode/captured_access execution 38.164 ms ###########
isolate/bytecode/call_frame execution 8.153 ms ######
isolate/bytecode/runtime_helpers execution 11.952 ms #######
isolate/bytecode/property_get execution 44.805 ms ###########
isolate/bytecode/property_set execution 40.533 ms ###########
isolate/bytecode/method_call execution 8.674 ms #######
isolate/bytecode/object_literal execution 13.273 ms ########
isolate/bytecode/array_literal execution 15.463 ms ########
exec/for_of execution 6.783 ms ######
exec/arithmetic_loop execution 1022.153 ms #####################
exec/object_construction execution 8.019 ms ######
exec/string_ops execution 2.024 ms ⚠ ###
pipeline/exec/lex frontend 0.031 ms #
pipeline/exec/parse frontend 0.027 ms #
pipeline/exec/evaluate execution 29.879 ms ##########
pipeline/closure_legacy/evaluate execution 29.273 ms ##########
pipeline/bytecode/compile frontend 0.023 ms ⚠ #
pipeline/bytecode/evaluate execution 11.388 ms #######
pipeline/parse_heavy frontend 0.542 ms #

Closure-conversion comparison

  • unavailable

… overrides

`get_array_prototype` can return `Array(...)`, `Proxy(...)`, or any other
Value when the prototype was overridden via `Object.setPrototypeOf`.  The
previous `_ => ()` wildcard silently swallowed all these cases, causing
`get_computed_property` to return `Undefined` even when Symbol.iterator
was reachable through the prototype chain.

Replace the wildcard with:
  - `Null | Undefined` → end of chain (same semantics as before)
  - any other Value → delegate to `self.get_computed_property`, which
    already dispatches through Array, Proxy, Map, Set, and Object arms

This makes the following cases work correctly:
  - `Object.setPrototypeOf(a, []); a[Symbol.iterator]`
  - `Object.setPrototypeOf(a, new Proxy(Array.prototype, {})); for (x of a)`

while preserving the fix from the parent commit: after a real
`delete Array.prototype[Symbol.iterator]`, the prototype chain walk via
the `Object(proto_data)` arm finds nothing, falls through to `Null | Undefined`,
and returns Undefined as required by GetIterator §7.4.1.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@dowdiness dowdiness merged commit 82cc167 into main Jun 25, 2026
15 checks passed
@dowdiness dowdiness deleted the worktree-typeerror-retriage branch June 25, 2026 15:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant