Skip to content

Commit 5f3037e

Browse files
woessansalond
authored andcommitted
[GR-62040] Backport to 24.2: Fix element-wise Array.prototype.splice.
PullRequest: js/3410
2 parents 9a5a328 + d57893c commit 5f3037e

21 files changed

+480
-286
lines changed
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
/*
2+
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* Licensed under the Universal Permissive License v 1.0 as shown at http://oss.oracle.com/licenses/upl.
6+
*/
7+
8+
load("./assert.js");
9+
10+
function assertArrayEquals(actual, expected) {
11+
if (actual.length !== expected.length) {
12+
throw new Error(`expected length: [${expected.length}] actual length: [${actual.length}]\nexpected array: [${expected}]\nactual array: [${actual}]`);
13+
}
14+
15+
for (let i = 0; i < expected.length; i++) {
16+
let expectedHas = expected.hasOwnProperty(i);
17+
let actualHas = actual.hasOwnProperty(i);
18+
if (actualHas !== expectedHas || actual[i] !== expected[i]) {
19+
throw new Error(`index: ${i} expected value: ${expectedHas ? expected[i] : "<empty>"} actual value: ${actualHas ? actual[i] : "<empty>"}\nexpected array: [${expected}]\nactual array: [${actual}]`);
20+
}
21+
}
22+
}
23+
24+
// splice block-wise
25+
var arr = [0, 1, 2, 3, 4, 5, , 7];
26+
var rem = arr.splice(2, 3);
27+
assertArrayEquals(arr, [0, 1, 5, ,7]);
28+
assertArrayEquals(rem, [2, 3, 4]);
29+
30+
// Invalidate no-prototype-elements assumption.
31+
Object.defineProperty(Object.prototype, "2", {value: "^2", configurable: true, writable: true});
32+
33+
// splice element-wise
34+
// itemCount < actualDeleteCount
35+
var arr = [0, 1, 2, 3, 4, 5, , 7];
36+
var rem = arr.splice(2, 3);
37+
assertArrayEquals(arr, [0, 1, 5, , 7]);
38+
assertArrayEquals(rem, [2, 3, 4]);
39+
40+
var arr = [0, 1, 2, 3, 4, 5, , 7];
41+
var rem = arr.splice(2, 3, 8, 9);
42+
assertArrayEquals(arr, [0, 1, 8, 9, 5, , 7]);
43+
assertArrayEquals(rem, [2, 3, 4]);
44+
45+
var arr = [0, 1, , , 4, , , 7, ,];
46+
var rem = arr.splice(0, 3);
47+
assertArrayEquals(arr, [ , 4, , , 7, ,]);
48+
assertArrayEquals(rem, [0, 1, "^2"]);
49+
50+
var arr = [0, 1, , , 4, 5, , 7, , 9];
51+
var rem = arr.splice(8, 2);
52+
assertArrayEquals(arr, [0, 1, , , 4, 5, , 7]);
53+
assertArrayEquals(rem, [ , 9]);
54+
55+
// itemCount > actualDeleteCount
56+
var arr = [0, 1, 2, 3, , 5, , , , 9];
57+
var rem = arr.splice(1, 2, 6, 7, 8);
58+
assertArrayEquals(arr, [0, 6, 7, 8, 3, , 5, , , , 9]);
59+
assertArrayEquals(rem, [1, 2]);
60+
61+
62+
// Proxy object
63+
// itemCount < actualDeleteCount
64+
arr = new Proxy([0, 1, , , 4, , , 7, ,], proxyHandler(
65+
"get(length)",
66+
"get(constructor)",
67+
"has(0)", "get(0)",
68+
"has(1)", "get(1)",
69+
"has(2)", "get(2)",
70+
"has(3)", "deleteProperty(0)",
71+
"has(4)", "get(4)", "set(1, 4)",
72+
"has(5)", "deleteProperty(2)",
73+
"has(6)", "deleteProperty(3)",
74+
"has(7)", "get(7)", "set(4, 7)",
75+
"has(8)", "deleteProperty(5)",
76+
"deleteProperty(8)",
77+
"deleteProperty(7)",
78+
"deleteProperty(6)",
79+
"set(length, 6)",
80+
));
81+
rem = Array.prototype.splice.call(arr, 0, 3);
82+
assertArrayEquals(arr, [ , 4, , , 7, ,]);
83+
assertArrayEquals(rem, [0, 1, "^2"]);
84+
85+
arr = new Proxy([0, 1, , , 4], proxyHandler(
86+
"get(length)",
87+
"get(constructor)",
88+
"has(3)",
89+
"has(4)", "get(4)", "set(3, 4)",
90+
"deleteProperty(4)",
91+
"set(length, 4)",
92+
));
93+
rem = Array.prototype.splice.call(arr, 3, 1);
94+
assertArrayEquals(arr, [0, 1, , 4]);
95+
assertArrayEquals(rem, [,]);
96+
97+
// itemCount > actualDeleteCount
98+
arr = new Proxy([0, 1, 2, 3, , 5, , , , 9], proxyHandler(
99+
"get(length)",
100+
"get(constructor)",
101+
"has(1)", "get(1)",
102+
"has(2)", "get(2)",
103+
"has(9)", "get(9)", "set(10, 9)",
104+
"has(8)", "deleteProperty(9)",
105+
"has(7)", "deleteProperty(8)",
106+
"has(6)", "deleteProperty(7)",
107+
"has(5)", "get(5)", "set(6, 5)",
108+
"has(4)", "deleteProperty(5)",
109+
"has(3)", "get(3)", "set(4, 3)",
110+
"set(1, 6)",
111+
"set(2, 7)",
112+
"set(3, 8)",
113+
"set(length, 11)",
114+
));
115+
rem = Array.prototype.splice.call(arr, 1, 2, 6, 7, 8);
116+
assertArrayEquals(arr, [0, 6, 7, 8, 3, , 5, , , , 9]);
117+
assertArrayEquals(rem, [1, 2]);
118+
119+
120+
// Array with Proxy prototype
121+
// itemCount < actualDeleteCount
122+
arr = Object.setPrototypeOf([0, 1, , , 4, , , 7, ,], new Proxy({}, proxyHandler(
123+
"get(constructor)",
124+
"has(2)",
125+
"get(2)",
126+
"has(3)",
127+
"has(5)",
128+
"has(6)",
129+
"has(8)",
130+
)));
131+
rem = Array.prototype.splice.call(arr, 0, 3);
132+
assertArrayEquals(arr, [ , 4, , , 7, ,]);
133+
assertArrayEquals(rem, [0, 1, "^2"]);
134+
135+
// itemCount > actualDeleteCount
136+
arr = Object.setPrototypeOf([0, 1, 2, 3, , 5, , , , 9], new Proxy({}, proxyHandler(
137+
"get(constructor)",
138+
"set(10, 9)",
139+
"has(8)",
140+
"has(7)",
141+
"has(6)",
142+
"set(6, 5)",
143+
"has(4)",
144+
"set(4, 3)",
145+
)));
146+
rem = Array.prototype.splice.call(arr, 1, 2, 6, 7, 8);
147+
assertArrayEquals(arr, [0, 6, 7, 8, 3, , 5, , , , 9]);
148+
assertArrayEquals(rem, [1, 2]);
149+
150+
151+
function proxyHandler(...expectedTraps) {
152+
let it = expectedTraps.values();
153+
function log(trap) {
154+
const expected = it.next().value;
155+
if (expected !== undefined) {
156+
assertSame(expected, trap);
157+
} else {
158+
assertTrue(trap.startsWith("get"));
159+
}
160+
}
161+
return {
162+
has(target, p) {
163+
log(`has(${String(p)})`);
164+
return Reflect.has(target, p);
165+
},
166+
get(target, p, receiver) {
167+
log(`get(${String(p)})`);
168+
return Reflect.get(target, p, receiver);
169+
},
170+
set(target, p, value, receiver) {
171+
log(`set(${String(p)}, ${value})`);
172+
return Reflect.set(target, p, value, receiver);
173+
},
174+
deleteProperty(target, p) {
175+
log(`deleteProperty(${String(p)})`);
176+
return Reflect.deleteProperty(target, p);
177+
},
178+
};
179+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/*
2+
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* Licensed under the Universal Permissive License v 1.0 as shown at http://oss.oracle.com/licenses/upl.
6+
*/
7+
8+
/**
9+
* Regression test for Array.prototype.splice on sparse arrays.
10+
*/
11+
12+
load("assert.js");
13+
14+
const assert = {
15+
sameValue(actual, expected) {
16+
assertSame(expected, actual);
17+
},
18+
compareArray(actual, expected) {
19+
assertSameContent(expected, actual);
20+
}
21+
}
22+
23+
for (const array of ([
24+
Array(10_000_000), // <= 2147483646
25+
{__proto__: Array.prototype, length: 1_000_000},
26+
])) {
27+
const largeLength = array.length;
28+
29+
// Make sparse array.
30+
array.splice(largeLength - 1, 1, 'lastElement');
31+
array.splice(largeLength / 2, 1, 'middleElement');
32+
array.splice(0, 0, 'firstElement');
33+
assert.sameValue(array.length, largeLength + 1);
34+
35+
array.splice(largeLength - 1, 2);
36+
assert.sameValue(array.length, largeLength - 1);
37+
38+
array.splice(largeLength - 1, 1, 'newElement');
39+
assert.sameValue(array.length, largeLength);
40+
assert.sameValue(array[largeLength - 1], 'newElement');
41+
42+
array.splice(largeLength - 40, 5);
43+
assert.sameValue(array.length, largeLength - 5);
44+
assert.sameValue(array[largeLength - 6], 'newElement');
45+
46+
array.splice(largeLength - 5, 1, 'newElement1', 'newElement2', 'newElement3');
47+
assert.sameValue(array.length, largeLength - 2);
48+
assert.compareArray(array.slice(largeLength - 5, largeLength - 2), ['newElement1', 'newElement2', 'newElement3']);
49+
50+
array.splice(largeLength - 7, 0, 'newElement0');
51+
assert.sameValue(array.length, largeLength - 1);
52+
assert.compareArray(array.slice(largeLength - 7, largeLength - 1), ['newElement0', , 'newElement', 'newElement1', 'newElement2', 'newElement3']);
53+
}

graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/runtime/InternalArrayTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* The Universal Permissive License (UPL), Version 1.0
@@ -576,7 +576,7 @@ public void testSparseArrayRemoveRange() {
576576
sparse.setElement(object, 601, UPPER_BORDER_1, false);
577577
sparse.setElement(object, 900, END, false);
578578

579-
sparse = sparse.removeRange(object, 400, 600); // deleting 201 elements
579+
sparse = sparse.removeRange(object, 400, 601); // deleting 201 elements
580580

581581
assertTrue(sparse instanceof SparseArray);
582582

0 commit comments

Comments
 (0)