Skip to content

Commit 63a8547

Browse files
committed
GROOVY-4843, GROOVY-8560: box primitive array for spread invocation
3_0_X backport
1 parent bef1fda commit 63a8547

File tree

4 files changed

+161
-21
lines changed

4 files changed

+161
-21
lines changed

src/main/java/org/codehaus/groovy/runtime/ScriptBytecodeAdapter.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -810,7 +810,8 @@ public static Object[] despreadList(Object[] args, Object[] spreads, int[] posit
810810
} else if (value instanceof List) {
811811
ret.addAll((List) value);
812812
} else if (value.getClass().isArray()) {
813-
Collections.addAll(ret, DefaultTypeTransformation.primitiveArrayBox(value));
813+
Collections.addAll(ret, value.getClass().getComponentType().isPrimitive()
814+
? DefaultTypeTransformation.primitiveArrayBox(value) : (Object[]) value);
814815
} else {
815816
String error = "Cannot spread the type " + value.getClass().getName() + " with value " + value;
816817
if (value instanceof Map) {

src/main/java/org/codehaus/groovy/vmplugin/v8/Selector.java

+24-11
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
import org.codehaus.groovy.runtime.metaclass.NewInstanceMetaMethod;
5050
import org.codehaus.groovy.runtime.metaclass.NewStaticMetaMethod;
5151
import org.codehaus.groovy.runtime.metaclass.ReflectionMetaMethod;
52+
import org.codehaus.groovy.runtime.typehandling.DefaultTypeTransformation;
5253
import org.codehaus.groovy.runtime.wrappers.Wrapper;
5354
import org.codehaus.groovy.vmplugin.VMPlugin;
5455
import org.codehaus.groovy.vmplugin.VMPluginFactory;
@@ -150,12 +151,24 @@ public static Selector getSelector(MutableCallSite callSite, Class<?> sender, St
150151
* number arguments.
151152
*/
152153
private static Object[] spread(Object[] args, boolean spreadCall) {
153-
if (!spreadCall) return args;
154-
Object[] normalArguments = (Object[]) args[1];
155-
Object[] ret = new Object[normalArguments.length + 1];
156-
ret[0] = args[0];
157-
System.arraycopy(normalArguments, 0, ret, 1, ret.length - 1);
158-
return ret;
154+
Object[] result = args;
155+
if (spreadCall) {
156+
Object[] arguments = (Object[]) args[1];
157+
final int nArguments = arguments.length;
158+
159+
result = new Object[nArguments + 1];
160+
result[0] = args[0]; // the receiver
161+
System.arraycopy(arguments, 0, result, 1, nArguments);
162+
163+
// accommodate Object[] spreader m. handle
164+
for (int i = 1; i <= nArguments; i += 1) {
165+
Class argumentType = result[i] != null ? result[i].getClass() : Object.class;
166+
if (argumentType.isArray() && argumentType.getComponentType().isPrimitive()) {
167+
result[i] = DefaultTypeTransformation.primitiveArrayBox(result[i]); // GROOVY-4843, GROOVY-8560
168+
}
169+
}
170+
}
171+
return result;
159172
}
160173

161174
private static class CastSelector extends MethodSelector {
@@ -518,7 +531,7 @@ public MethodSelector(MutableCallSite callSite, Class<?> sender, String methodNa
518531
this.safeNavigation = safeNavigation && arguments[0] == null;
519532
this.thisCall = thisCall;
520533
this.spread = spreadCall;
521-
this.cache = !spread;
534+
this.cache = !spreadCall;
522535

523536
if (LOG_ENABLED) {
524537
StringBuilder msg =
@@ -764,8 +777,7 @@ public void correctParameterLength() {
764777
Class<?>[] params = handle.type().parameterArray();
765778
if (currentType != null) params = currentType.parameterArray();
766779
if (!isVargs) {
767-
if (spread && useMetaClass) return;
768-
if (params.length == 2 && args.length == 1) {
780+
if (!(spread && useMetaClass) && params.length == 2 && args.length == 1) {
769781
handle = MethodHandles.insertArguments(handle, 1, SINGLE_NULL_ARRAY);
770782
}
771783
return;
@@ -861,8 +873,9 @@ public void correctNullReceiver() {
861873
}
862874

863875
public void correctSpreading() {
864-
if (!spread || useMetaClass || skipSpreadCollector) return;
865-
handle = handle.asSpreader(Object[].class, args.length - 1);
876+
if (spread && !useMetaClass && !skipSpreadCollector) {
877+
handle = handle.asSpreader(Object[].class, args.length - 1);
878+
}
866879
}
867880

868881
/**

src/test/groovy/SpreadArgTest.groovy

+106
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package groovy
20+
21+
import org.junit.Test
22+
23+
import static groovy.test.GroovyAssert.assertScript
24+
25+
/**
26+
* Tests for the spread arg(s) operator "m(*x)".
27+
*/
28+
final class SpreadArgTest {
29+
30+
// GROOVY-9515
31+
@Test
32+
void testSpreadList() {
33+
assertScript '''
34+
int f(int x, int y) { x + y }
35+
int f(int x) { x }
36+
int g(x) { f(*x) }
37+
38+
assert g([1]) == 1
39+
assert g([1, 2]) == 3
40+
'''
41+
}
42+
43+
@Test
44+
void testSpreadArray() {
45+
assertScript '''
46+
int f(int x, int y, int z) {
47+
x + y + z
48+
}
49+
50+
Number[] nums = [1, 2, 39]
51+
assert f(*nums) == 42
52+
'''
53+
}
54+
55+
// GROOVY-8560
56+
@Test
57+
void testSpreadArray2() {
58+
assertScript '''
59+
def f(byte[] bytes) {
60+
assert bytes.length == 2
61+
}
62+
63+
def ba = new byte[]{0, 0}
64+
def oa = new Object[]{ba}
65+
f(oa[0])
66+
f( *oa )
67+
'''
68+
}
69+
70+
// GROOVY-5647
71+
@Test
72+
void testSpreadSkipSTC() {
73+
assertScript '''
74+
import groovy.transform.CompileStatic
75+
import static groovy.transform.TypeCheckingMode.SKIP
76+
77+
@CompileStatic
78+
class C {
79+
@CompileStatic(SKIP)
80+
def foo(fun, args) {
81+
new Runnable() { // create an anonymous class which should *not* be visited
82+
void run() {
83+
fun(*args) // spread operator is disallowed with STC/SC, but SKIP should prevent from an error
84+
}
85+
}
86+
}
87+
}
88+
89+
new C()
90+
'''
91+
}
92+
93+
@Test
94+
void testSpreadVarargs() {
95+
assertScript '''
96+
int f(String... strings) {
97+
g(*strings)
98+
}
99+
int g(String... strings) {
100+
strings.length
101+
}
102+
103+
assert f("1","2") == 2
104+
'''
105+
}
106+
}

src/test/groovy/mock/interceptor/MockForJavaTest.groovy

+29-9
Original file line numberDiff line numberDiff line change
@@ -18,20 +18,22 @@
1818
*/
1919
package groovy.mock.interceptor
2020

21-
import groovy.test.GroovyTestCase
21+
import org.junit.Test
2222

23-
class MockForJavaTest extends GroovyTestCase {
23+
final class MockForJavaTest {
24+
25+
@Test
2426
void testIterator() {
2527
def iteratorContext = new MockFor(Iterator)
2628
iteratorContext.demand.hasNext() { true }
2729
iteratorContext.demand.hasNext() { true }
2830
iteratorContext.demand.hasNext() { false }
2931
def iterator = iteratorContext.proxyDelegateInstance()
30-
iteratorContext.demand.next() { "foo" }
32+
iteratorContext.demand.next() { 'foo' }
3133
def iterator2 = iteratorContext.proxyDelegateInstance()
3234

3335
assert new IteratorCounter().count(iterator2) == 2
34-
assert iterator2.next() == "foo"
36+
assert iterator2.next() == 'foo'
3537
iteratorContext.verify(iterator2)
3638

3739
assert new IteratorCounter().count(iterator) == 2
@@ -45,13 +47,31 @@ class MockForJavaTest extends GroovyTestCase {
4547
iteratorContext.verify(iterator3)
4648
}
4749

50+
// GROOVY-4843
51+
@Test
52+
void testStream() {
53+
def streamContext = new MockFor(FileInputStream)
54+
streamContext.demand.read(1..1) { byte[] b ->
55+
b[0] = 1
56+
b[1] = 2
57+
return 2
58+
}
59+
def p = System.getProperty('os.name').contains('Windows') ? 'NUL' : '/dev/null'
60+
def s = streamContext.proxyDelegateInstance(p)
61+
byte[] buffer = new byte[2]
62+
assert s.read(buffer) == 2
63+
assert buffer[0] == 1
64+
assert buffer[1] == 2
65+
streamContext.verify(s)
66+
}
67+
68+
@Test
4869
void testString() {
4970
def stringContext = new MockFor(String)
50-
stringContext.demand.endsWith(2..2) { String arg -> arg == "foo" }
71+
stringContext.demand.endsWith(2..2) { String arg -> arg == 'foo' }
5172
def s = stringContext.proxyDelegateInstance()
52-
assert !s.endsWith("bar")
53-
assert s.endsWith("foo")
73+
assert !s.endsWith('bar')
74+
assert s.endsWith('foo')
5475
stringContext.verify(s)
5576
}
56-
57-
}
77+
}

0 commit comments

Comments
 (0)