fix(array): a replaced Array.prototype[Symbol.iterator] drives spread, call-spread and Array.from (#7542) - #7759
Conversation
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (4)
📝 WalkthroughWalkthroughArray spread now honors modified ChangesPatched Array Iterator Semantics
Estimated code review effort: 3 (Moderate) | ~20 minutes Sequence Diagram(s)sequenceDiagram
participant ArrayOperation
participant ArrayIteratorRuntime
participant ArrayPrototype
ArrayOperation->>ArrayIteratorRuntime: Detect modified prototype iterator
ArrayIteratorRuntime->>ArrayPrototype: Read patched Symbol.iterator
ArrayPrototype-->>ArrayIteratorRuntime: Return iterator-produced values
ArrayIteratorRuntime-->>ArrayOperation: Materialize spread result
Possibly related issues
Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
8c50f31 to
4e0c716
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@crates/perry-runtime/src/array/iterator.rs`:
- Around line 820-824: Update the shortcut in the iterator conversion path
around array_proto_iterator_modified and js_array_is_array so Proxy receivers do
not enter the prototype-only js_get_iterator path; route them through generic
iterator lookup or an implementation that performs Proxy [[Get]]. Add coverage
for spread, Array.from, and function-spread calls using a Proxy with both a get
trap and a target-owned iterator.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: ca49f96c-6a1d-4d1d-8df1-c3986d25c9f8
📒 Files selected for processing (6)
changelog.d/7759-array-proto-iterator-spread.mdcrates/perry-runtime/src/array/from_concat.rscrates/perry-runtime/src/array/iterator.rscrates/perry-runtime/src/array/mod.rscrates/perry-runtime/src/array/push_pop.rscrates/perry-runtime/src/object/arguments.rs
…, call-spread and Array.from (#7542)
4e0c716 to
b3568c1
Compare
Merging as v0.5.1445A/B'd on my own host — The two corrections are worth more than the fixYou implemented the issue's suggested fix, measured it, and found the arm is never reached. "I first implemented the fix exactly where the issue points, at the That is the difference between fixing the reported location and fixing the bug, and it is why a single-site fix was never going to work: four entry points reach an array by different routes. Delegating all four to The Declining to write a gap test is the right callAny test that patches The detail that it only appeared through the harness, not by hand ( Three follow-ups filed rather than folded in
|
Fixes #7542.
Two corrections to the diagnosis, both of which changed the fix
1.
note_array_proto_iterator_writedoes fire (the issue's open check #1). Instrumented: 49 calls during a run, exactly one matches and sets the flag.So the flag was never the problem, and neither
js_get_iterator's path nor this one is dead.2. The walk does NOT miss for a plain array. The issue says the generic lookup "reads own symbol props only … so for a plain array it misses and falls to the
js_array_is_arrayarm". It doesn't:js_object_get_symbol_propertysynthesizes the built-inSymbol.iteratorfor an array receiver (ajs_class_method_bindby name) rather than reading the prototype slot — so the walk resolves the builtin, calls it, and returns the element copy.I know this because I first implemented the fix exactly where the issue points, at the
js_array_is_arrayarm, and it changed nothing. Instrumenting that arm showed it is never reached with the flag set. A guard there is dead code.The fix
The guard goes before the walk, and at each entry point that reaches an array by a different route — this is why a single-site fix was never going to work:
array_from_spread_value[...arr]js_array_from_valueArray.from(arr)js_array_clonejs_array_push_spread_f64[0, ...arr, 9]js_array_like_to_arrayf(...arr)Each delegates to
js_get_iterator— the one implementation that consults the patched prototype per GetIterator (read the method offArray.prototype, call it withthis === val, TypeError when deleted or non-callable). Delegating rather than restating that sequence four times is the point: two copies of a spec sequence that must agree is how this diverged in the first place.An own
arr[Symbol.iterator]still wins — the shortcut stands aside when the receiver carries its own method, becausejs_get_iterator's patched branch reads the prototype only and would otherwise throw "not iterable" for an array with an own method once the prototype slot is deleted. (That case caught me: my first cut broke it.)array_proto_iterator_modified()is sticky-false until user code writes the prototype slot, and #7533's dense fast path already declines when it is set, so ordinary programs are untouched.Verification
Byte-identical. Before this change the four spread rows were
[1,2,3]/[0,1,2,3,9]/3/[1,2,3].cargo test -p perry-runtime --lib: 1991 passed, 0 failed.cargo fmt --all --check,scripts/check_file_size.sh: clean.No gap test, deliberately
Any test that patches
Array.prototype[Symbol.iterator]takes the oracle down under the parity harness: node builds aSafeMapfrom an iterable, gets the patched value, and exits 1 withso the comparison runs against a crashed reference. (Standalone
node file.tsis fine, which is why this only showed up once I ran it through the harness rather than by hand.) I tried four ways around it — restore by saved reference, restore viagetOwnPropertyDescriptor, a behavioural replacement, a self-iterable replacement, andprocess.exit(0)— and none works, for reasons that are themselves bugs (below). So the fix is verified by direct comparison instead, and I would rather say that plainly than land a test that passes for the wrong reason.Filed separately (found here, out of scope)
for…ofover a typed local ignores the patched method. It is a codegen dense index loop with no runtime call at all (js_array_values_iter_obj/js_array_lengthappear only in thedeclareblock) — a different mechanism from the four above, needing its own guard.arrProto[Symbol.iterator] = originalthen[...arr]throwsnext is not a function: readingArray.prototype[Symbol.iterator]yields a method bound to the prototype. Pre-existing and independent of this change — it reproduces onfor…ofover an array literal, which routes throughjs_get_iteratorand is untouched here.Object.getOwnPropertyDescriptor(Array.prototype, Symbol.iterator)returnsundefined.No version bump (maintainer bumps at merge).
Summary by CodeRabbit
Array.prototype[Symbol.iterator]behavior.Array.from, array spreading, spreading into function calls, array appending, and related conversions.