Skip to content

Commit e45a671

Browse files
committed
[GR-21412] JS: removeArrayElement should remove the element and shift the rest, shrinking the array size by one.
PullRequest: js/1353
2 parents d8ea2e9 + 49257f7 commit e45a671

File tree

8 files changed

+560
-115
lines changed

8 files changed

+560
-115
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,317 @@
1+
/*
2+
* Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* The Universal Permissive License (UPL), Version 1.0
6+
*
7+
* Subject to the condition set forth below, permission is hereby granted to any
8+
* person obtaining a copy of this software, associated documentation and/or
9+
* data (collectively the "Software"), free of charge and under any and all
10+
* copyright rights in the Software, and any and all patent rights owned or
11+
* freely licensable by each licensor hereunder covering either (i) the
12+
* unmodified Software as contributed to or provided by such licensor, or (ii)
13+
* the Larger Works (as defined below), to deal in both
14+
*
15+
* (a) the Software, and
16+
*
17+
* (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
18+
* one is included with the Software each a "Larger Work" to which the Software
19+
* is contributed by such licensors),
20+
*
21+
* without restriction, including without limitation the rights to copy, create
22+
* derivative works of, display, perform, and distribute the Software and make,
23+
* use, sell, offer for sale, import, export, have made, and have sold the
24+
* Software and the Larger Work(s), and to sublicense the foregoing rights on
25+
* either these or other terms.
26+
*
27+
* This license is subject to the following condition:
28+
*
29+
* The above copyright notice and either this complete permission notice or at a
30+
* minimum a reference to the UPL must be included in all copies or substantial
31+
* portions of the Software.
32+
*
33+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
34+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
35+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
36+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
37+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
38+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
39+
* SOFTWARE.
40+
*/
41+
package com.oracle.truffle.js.test.interop;
42+
43+
import static com.oracle.truffle.js.lang.JavaScriptLanguage.ID;
44+
import static org.junit.Assert.assertEquals;
45+
import static org.junit.Assert.assertFalse;
46+
import static org.junit.Assert.assertTrue;
47+
48+
import java.util.ArrayList;
49+
import java.util.Arrays;
50+
import java.util.List;
51+
import java.util.function.Consumer;
52+
53+
import org.graalvm.polyglot.Context;
54+
import org.graalvm.polyglot.TypeLiteral;
55+
import org.graalvm.polyglot.Value;
56+
import org.graalvm.polyglot.proxy.ProxyArray;
57+
import org.junit.After;
58+
import org.junit.Before;
59+
import org.junit.Test;
60+
61+
import com.oracle.truffle.js.test.JSTest;
62+
63+
public class ArrayPrototypeInteropTest {
64+
65+
private Context context;
66+
67+
@Before
68+
public void setUp() {
69+
context = JSTest.newContextBuilder().build();
70+
}
71+
72+
@After
73+
public void tearDown() {
74+
context.close();
75+
}
76+
77+
@Test
78+
public void testSplice() {
79+
testWithArray("Array.prototype.splice.call(a, 1, 2)",
80+
Arrays.asList(10, 20, 30, 40, 50),
81+
Arrays.asList(10, 40, 50),
82+
Arrays.asList(20, 30));
83+
testWithArray("Array.prototype.splice.call(a, 1, 2, 70, 80, 90)",
84+
Arrays.asList(10, 20, 30, 40, 50),
85+
Arrays.asList(10, 70, 80, 90, 40, 50),
86+
Arrays.asList(20, 30));
87+
testWithArray("Array.prototype.splice.call(a, 1, 3, 70, 80)",
88+
Arrays.asList(10, 20, 30, 40, 50),
89+
Arrays.asList(10, 70, 80, 50),
90+
Arrays.asList(20, 30, 40));
91+
testWithArray("Array.prototype.splice.call(a, 3, 10)",
92+
Arrays.asList(10, 20, 30, 40, 50),
93+
Arrays.asList(10, 20, 30),
94+
Arrays.asList(40, 50));
95+
testWithArray("Array.prototype.splice.call(a, -1, 0, 70)",
96+
Arrays.asList(10, 20, 30, 40, 50),
97+
Arrays.asList(10, 20, 30, 40, 70, 50),
98+
Arrays.asList());
99+
testWithArray("Array.prototype.splice.call(a, 0, 0, 70)",
100+
Arrays.asList(10, 20, 30, 40, 50),
101+
Arrays.asList(70, 10, 20, 30, 40, 50),
102+
Arrays.asList());
103+
testWithArray("Array.prototype.splice.call(a, 0, 1, 70)",
104+
Arrays.asList(10, 20, 30, 40, 50),
105+
Arrays.asList(70, 20, 30, 40, 50),
106+
Arrays.asList(10));
107+
}
108+
109+
@Test
110+
public void testPop() {
111+
testWithArray("Array.prototype.pop.call(a)",
112+
Arrays.asList(10, 20, 30, 40, 50),
113+
Arrays.asList(10, 20, 30, 40),
114+
50);
115+
testWithArray("Array.prototype.pop.call(a)",
116+
Arrays.asList(10),
117+
Arrays.asList(),
118+
10);
119+
testWithArray("Array.prototype.pop.call(a)",
120+
Arrays.asList(),
121+
Arrays.asList(),
122+
result -> assertTrue(result.isNull()));
123+
}
124+
125+
@Test
126+
public void testShift() {
127+
testWithArray("Array.prototype.shift.call(a)",
128+
Arrays.asList(10, 20, 30, 40, 50),
129+
Arrays.asList(20, 30, 40, 50),
130+
10);
131+
testWithArray("Array.prototype.shift.call(a)",
132+
Arrays.asList(10),
133+
Arrays.asList(),
134+
10);
135+
testWithArray("Array.prototype.shift.call(a)",
136+
Arrays.asList(),
137+
Arrays.asList(),
138+
result -> assertTrue(result.isNull()));
139+
}
140+
141+
@Test
142+
public void testPush() {
143+
testWithArray("Array.prototype.push.call(a, 80)",
144+
Arrays.asList(10, 20, 30, 40),
145+
Arrays.asList(10, 20, 30, 40, 80),
146+
5);
147+
testWithArray("Array.prototype.push.call(a, 10)",
148+
Arrays.asList(),
149+
Arrays.asList(10),
150+
1);
151+
testWithArray("Array.prototype.push.call(a)",
152+
Arrays.asList(10, 20, 30, 40),
153+
Arrays.asList(10, 20, 30, 40),
154+
4);
155+
testWithArray("Array.prototype.push.call(a, 80, 90)",
156+
Arrays.asList(10, 20),
157+
Arrays.asList(10, 20, 80, 90),
158+
4);
159+
}
160+
161+
@Test
162+
public void testReverse() {
163+
testWithArray("Array.prototype.reverse.call(a)",
164+
Arrays.asList(10, 20, 30, 40, 50),
165+
Arrays.asList(50, 40, 30, 20, 10),
166+
Arrays.asList(50, 40, 30, 20, 10));
167+
}
168+
169+
@Test
170+
public void testSort() {
171+
testWithArray("Array.prototype.sort.call(a)",
172+
Arrays.asList(50, 40, 30, 20, 10),
173+
Arrays.asList(10, 20, 30, 40, 50),
174+
Arrays.asList(10, 20, 30, 40, 50));
175+
testWithArray("Array.prototype.sort.call(a, (x, y) => y - x)",
176+
Arrays.asList(10, 20, 30, 40, 50),
177+
Arrays.asList(50, 40, 30, 20, 10),
178+
Arrays.asList(50, 40, 30, 20, 10));
179+
}
180+
181+
@Test
182+
public void testUnshift() {
183+
testWithArray("Array.prototype.unshift.call(a, 80)",
184+
Arrays.asList(10, 20, 30, 40),
185+
Arrays.asList(80, 10, 20, 30, 40),
186+
5);
187+
testWithArray("Array.prototype.unshift.call(a, 10)",
188+
Arrays.asList(),
189+
Arrays.asList(10),
190+
1);
191+
testWithArray("Array.prototype.unshift.call(a)",
192+
Arrays.asList(10, 20, 30, 40),
193+
Arrays.asList(10, 20, 30, 40),
194+
4);
195+
testWithArray("Array.prototype.unshift.call(a, 80, 90)",
196+
Arrays.asList(10, 20),
197+
Arrays.asList(80, 90, 10, 20),
198+
4);
199+
}
200+
201+
@Test
202+
public void testCopyWithin() {
203+
testWithArray("Array.prototype.copyWithin.call(a, 4, 1, 3)",
204+
Arrays.asList(10, 20, 30, 40, 50, 60),
205+
Arrays.asList(10, 20, 30, 40, 20, 30),
206+
Arrays.asList(10, 20, 30, 40, 20, 30));
207+
testWithArray("Array.prototype.copyWithin.call(a, 1, 4)",
208+
Arrays.asList(10, 20, 30, 40, 50, 60),
209+
Arrays.asList(10, 50, 60, 40, 50, 60),
210+
Arrays.asList(10, 50, 60, 40, 50, 60));
211+
}
212+
213+
@Test
214+
public void testFill() {
215+
testWithArray("Array.prototype.fill.call(a, 69)",
216+
Arrays.asList(10, 20, 30, 40, 50, 60),
217+
Arrays.asList(69, 69, 69, 69, 69, 69),
218+
Arrays.asList(69, 69, 69, 69, 69, 69));
219+
testWithArray("Array.prototype.fill.call(a, 69)",
220+
Arrays.asList(),
221+
Arrays.asList(),
222+
Arrays.asList());
223+
}
224+
225+
@Test
226+
public void testDelete() {
227+
testWithArray("delete a[0]",
228+
Arrays.asList(10, 20, 30, 40, 50),
229+
Arrays.asList(10, 20, 30, 40, 50),
230+
result -> assertFalse(result.asBoolean()));
231+
testWithArray("delete a[5]",
232+
Arrays.asList(10, 20, 30, 40, 50),
233+
Arrays.asList(10, 20, 30, 40, 50),
234+
result -> assertTrue(result.asBoolean()));
235+
}
236+
237+
private void testWithArray(String test, List<Integer> before, List<Integer> afterExpected, List<Integer> expectedResult) {
238+
testWithArray(test, before, afterExpected, actualResult -> assertEquals("result", expectedResult, actualResult.as(LIST_OF_INTEGER)));
239+
}
240+
241+
private void testWithArray(String test, List<Integer> before, List<Integer> afterExpected, int expectedResult) {
242+
testWithArray(test, before, afterExpected, actualResult -> assertEquals("result", expectedResult, actualResult.asInt()));
243+
}
244+
245+
private void testWithArray(String test, List<Integer> before, List<Integer> afterExpected, Consumer<Value> resultTest) {
246+
List<Object> values = new ArrayList<>(before);
247+
context.getBindings(ID).putMember("a", new MyProxyArray(values));
248+
Value resultValue = context.eval(ID, test);
249+
List<Integer> afterValue = new ArrayList<>(context.getBindings(ID).getMember("a").as(LIST_OF_INTEGER));
250+
assertEquals("array", afterExpected, afterValue);
251+
resultTest.accept(resultValue);
252+
}
253+
254+
private static final TypeLiteral<List<Integer>> LIST_OF_INTEGER = new TypeLiteral<List<Integer>>() {
255+
};
256+
257+
private static final class MyProxyArray implements ProxyArray {
258+
private final List<Object> values;
259+
260+
private MyProxyArray(List<Object> values) {
261+
this.values = values;
262+
}
263+
264+
@Override
265+
public Object get(long index) {
266+
return values.get(checkIndex(index));
267+
}
268+
269+
@Override
270+
public void set(long index, Value value) {
271+
int idx = checkIndex(index);
272+
Object val = devalue(value);
273+
if (idx < getSize()) {
274+
values.set(idx, val);
275+
} else {
276+
while (idx > getSize()) {
277+
values.add(null);
278+
}
279+
assert idx == getSize();
280+
values.add(val);
281+
}
282+
}
283+
284+
@Override
285+
public boolean remove(long index) {
286+
values.remove(checkIndex(index));
287+
return true;
288+
}
289+
290+
@Override
291+
public long getSize() {
292+
return values.size();
293+
}
294+
295+
private static int checkIndex(long index) {
296+
if (index > Integer.MAX_VALUE || index < 0) {
297+
throw new ArrayIndexOutOfBoundsException("invalid index.");
298+
}
299+
return (int) index;
300+
}
301+
302+
private static Object devalue(Value value) {
303+
if (value.isHostObject()) {
304+
return value.asHostObject();
305+
} else if (value.fitsInInt()) {
306+
return value.asInt();
307+
} else {
308+
return value;
309+
}
310+
}
311+
312+
@Override
313+
public String toString() {
314+
return values.toString();
315+
}
316+
}
317+
}

graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/interop/InteropArrayTest.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,38 @@ public void testArgumentsObjectGetMembers() {
184184
}
185185
}
186186

187+
/**
188+
* Test that holes in JS arrays are readable and writable (i.e. getArrayElement and
189+
* setArrayElement work on them, respectively).
190+
*/
191+
@Test
192+
public void testArrayHoles() {
193+
try (Context context = JSTest.newContextBuilder().build()) {
194+
Value array = context.eval(ID, "[3,,,5]");
195+
assertEquals(4, array.getArraySize());
196+
assertEquals(3, array.getArrayElement(0).asInt());
197+
assertEquals(5, array.getArrayElement(3).asInt());
198+
assertTrue(array.getArrayElement(1).isNull());
199+
assertTrue(array.getArrayElement(2).isNull());
200+
array.setArrayElement(1, 4);
201+
array.setArrayElement(2, 1);
202+
assertEquals(4, array.getArrayElement(1).asInt());
203+
assertEquals(1, array.getArrayElement(2).asInt());
204+
}
205+
}
206+
207+
/**
208+
* Test reading out of bounds.
209+
*/
210+
@Test(expected = ArrayIndexOutOfBoundsException.class)
211+
public void testArrayIndexOutOfBounds() {
212+
try (Context context = JSTest.newContextBuilder().build()) {
213+
Value array = context.eval(ID, "[3, 4, 1, 5]");
214+
assertEquals(4, array.getArraySize());
215+
array.getArrayElement(4);
216+
}
217+
}
218+
187219
private static final int[] JAVA_ARRAY = new int[]{3, 4, 1, 5};
188220
private static final List<Integer> JAVA_LIST = Arrays.stream(JAVA_ARRAY).boxed().collect(Collectors.toList());
189221
private static final String JS_ARRAY_STRING = Arrays.toString(JAVA_ARRAY);

graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/polyglot/PolyglotBuiltinTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -155,9 +155,9 @@ public void testWrite() {
155155

156156
@Test
157157
public void testRemove() {
158-
assertEquals("true", test("var a = [1,2,3]; ''+(Polyglot.remove(a,1) && a[1] === undefined);"));
158+
assertEquals("1,3", test("var a = [1,2,3]; ''+(Polyglot.remove(a,1) && a);"));
159159
assertEquals("true", test("var o = {a:1, b:'foo'}; ''+(Polyglot.remove(o,'a') && o.a === undefined && o.b === 'foo');"));
160-
test("''+Polyglot.remove(false,0);", "non-interop object");
160+
test("''+Polyglot.remove(false, 0);", "non-interop object");
161161
}
162162

163163
@Test

0 commit comments

Comments
 (0)