-
-
Notifications
You must be signed in to change notification settings - Fork 132
fix(object): #5347 — Object.assign/spread with an array source no longer crashes #5527
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
+149
−29
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| // Gap test for #5347 — Object.assign / object-spread with an ARRAY source. | ||
| // An array source's indexed elements live in the ArrayHeader element buffer, | ||
| // not in an ObjectHeader keys_array; reading that field off an array used to | ||
| // deref garbage and SIGSEGV. Also covers the boxed-String target read-only | ||
| // negative (assigning an in-range index to a primitive-string target throws). | ||
| // Compared byte-for-byte against `node --experimental-strip-types`. | ||
|
|
||
| // ---- array source: indexed elements are copied (was a crash) ---- | ||
| console.log(JSON.stringify(Object.assign({}, [1, 2]))); // {"0":1,"1":2} | ||
| console.log(JSON.stringify({ ...[10, 20, 30] })); // {"0":10,"1":20,"2":30} | ||
| console.log(JSON.stringify(Object.assign({ x: 1 }, [7, 8]))); // {"x":1,"0":7,"1":8} | ||
| console.log(JSON.stringify(Object.assign({}, { a: 1 }, [9], { b: 2 }))); // {"a":1,"0":9,"b":2} | ||
|
|
||
| // ---- array source with a named expando: indices THEN expando, in order ---- | ||
| const arr: any = [1, 2]; | ||
| arr.foo = "z"; | ||
| console.log(JSON.stringify(Object.assign({}, arr))); // {"0":1,"1":2,"foo":"z"} | ||
|
|
||
| // ---- array target still grows from an array source ---- | ||
| console.log(JSON.stringify(Object.assign([0, 0, 0], [9, 9]))); // [9,9,0] | ||
|
|
||
| // ---- boxed-String target: an in-range index write is read-only -> TypeError ---- | ||
| function thrown(fn: () => void): string { | ||
| try { fn(); return "no throw"; } catch (e: any) { return e.constructor.name; } | ||
| } | ||
| console.log(thrown(() => Object.assign("a", [1]))); // TypeError | ||
| console.log(thrown(() => Object.assign("abc", { 1: "x" }))); // TypeError | ||
| console.log(thrown(() => Object.assign("abc", { 5: "x" }))); // no throw (out of range) | ||
| console.log(thrown(() => Object.assign("", { 0: "x" }))); // no throw (empty string) | ||
|
|
||
| // ---- normal object merges still must not throw ---- | ||
| console.log(JSON.stringify(Object.assign({ a: 1 }, { b: 2 }))); // {"a":1,"b":2} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
Repository: PerryTS/perry
Length of output: 32713
🌐 Web query:
Does Object.assign copy holes from sparse arrays as undefined or skip them?💡 Result:
Object.assign does NOT copy array “holes” from a sparse array as
undefined. Instead, it only copies that source’s enumerable own properties; indices that are holes have no own property key, so they are skipped and remain absent in the result. [1][2] In other words: - If the source sparse array has an actual element value at index i (even if that value isundefined), Object.assign will create/overwrite key “i” on the target with that value. [1][2] - If the source has a hole at index i (no own property for “i”), Object.assign does nothing for that key; the target will not get a property for that index (so it won’t become a “filled”undefinedslot). [1][2] Example behavior is discussed in practice: when assigning from a sparse array, empty slots are preserved as empty (non-existing) rather than turned intoundefinedvalues. [3] To “materialize” holes as actualundefinedvalues, you need a different operation (e.g., some array-copying methods that preserve empties vs ones that convert them); spread/other copying mechanisms can treat holes differently. [3][4]Citations:
Holes in sparse arrays are incorrectly materialized as
undefinedproperties instead of being skipped.The code calls
js_array_get(arr, i)which converts holes toundefined(per issue#323hole handling), then assigns that to the target object viaobject_assign_set_string_key. Per the ES spec and Node behavior,Object.assignshould skip holes entirely (they are absent own properties), not createundefinedproperties.For dense arrays (literals, push), this works correctly since there are no holes. For sparse arrays like
Object.assign({}, [1,,3]), index 1 becomesundefinedrather than remaining absent.Add an explicit hole check before assignment:
This pattern is used elsewhere in the codebase (e.g.,
flat_clone.rs,field_get_set.rs) to properly handle holes.🤖 Prompt for AI Agents