Skip to content

Commit 406feaf

Browse files
committed
GROOVY-11967: VerifyError in @CompileStatic constructor with default-valued list parameter
1 parent 9d59d71 commit 406feaf

2 files changed

Lines changed: 154 additions & 0 deletions

File tree

src/main/java/org/codehaus/groovy/transform/sc/transformers/ListExpressionTransformer.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import java.util.List;
3939

4040
import static org.codehaus.groovy.classgen.AsmClassGenerator.containsSpreadExpression;
41+
import static org.objectweb.asm.Opcodes.CHECKCAST;
4142
import static org.objectweb.asm.Opcodes.DUP;
4243
import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
4344

@@ -111,6 +112,16 @@ public void visit(final GroovyCodeVisitor visitor) {
111112
var list = new ConstructorCallExpression(ArrayList_TYPE, new ConstantExpression(getExpressions().size(), true));
112113
list.putNodeMetaData(StaticTypesMarker.DIRECT_METHOD_CALL_TARGET, ArrayList_NEW);
113114
list.visit(visitor);
115+
// GROOVY-11967: when the constructor goes through a dynamic call
116+
// site (indy or non-indy), the call leaves Object on the JVM stack
117+
// and the following INVOKEVIRTUAL ArrayList.add fails verification
118+
// unless preceded by CHECKCAST. The direct INVOKESPECIAL path of
119+
// StaticInvocationWriter already leaves ArrayList on the stack, so
120+
// there the cast is unnecessary.
121+
if (!ArrayList_TYPE.equals(os.getTopOperand())) {
122+
mv.visitTypeInsn(CHECKCAST, "java/util/ArrayList");
123+
os.replace(ArrayList_TYPE);
124+
}
114125

115126
for (Expression li : getExpressions()) {
116127
mv.visitInsn(DUP);
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
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 bugs
20+
21+
import org.codehaus.groovy.control.CompilerConfiguration
22+
import org.junit.jupiter.api.Test
23+
24+
import static groovy.test.GroovyAssert.assertScript
25+
26+
/**
27+
* Regression coverage for the synthesised lower-arity bridge constructor that
28+
* {@code @CompileStatic} emits for a constructor with a default-valued list
29+
* parameter. The bridge inlines the default {@code [...]} literal as a
30+
* {@code new ArrayList(n)} followed by {@code .add(...)} calls; when the
31+
* constructor goes through a dynamic call site (which happens in both indy
32+
* and non-indy modes for this bridge) it returns {@code Object} on the JVM
33+
* stack, so the {@code INVOKEVIRTUAL ArrayList.add} that follows must be
34+
* preceded by a {@code CHECKCAST ArrayList} to satisfy the verifier.
35+
*/
36+
final class Groovy11967 {
37+
38+
@Test
39+
void testDefaultListClassParam() {
40+
assertScript '''
41+
interface MessageSource {}
42+
@groovy.transform.CompileStatic
43+
class C {
44+
List<Class> types
45+
C(Class<?> a, MessageSource b, List<Class> targetTypes = [Object]) {
46+
this.types = targetTypes
47+
}
48+
}
49+
class FakeMs implements MessageSource {}
50+
51+
def c = new C(String, new FakeMs())
52+
assert c.types == [Object]
53+
'''
54+
}
55+
56+
@Test
57+
void testDefaultListStringParam() {
58+
assertScript '''
59+
@groovy.transform.CompileStatic
60+
class C {
61+
List<String> values
62+
C(int x, List<String> values = ['hi']) { this.values = values }
63+
}
64+
assert new C(1).values == ['hi']
65+
'''
66+
}
67+
68+
@Test
69+
void testDefaultListIntegerParam() {
70+
assertScript '''
71+
@groovy.transform.CompileStatic
72+
class C {
73+
List<Integer> values
74+
C(int x, List<Integer> values = [1, 2, 3]) { this.values = values }
75+
}
76+
assert new C(1).values == [1, 2, 3]
77+
'''
78+
}
79+
80+
@Test
81+
void testDefaultNestedListParam() {
82+
assertScript '''
83+
@groovy.transform.CompileStatic
84+
class C {
85+
List<List<String>> values
86+
C(int x, List<List<String>> values = [['a'], ['b']]) { this.values = values }
87+
}
88+
assert new C(1).values == [['a'], ['b']]
89+
'''
90+
}
91+
92+
@Test
93+
void testDefaultEmptyListParam() {
94+
assertScript '''
95+
@groovy.transform.CompileStatic
96+
class C {
97+
List<String> values
98+
C(int x, List<String> values = []) { this.values = values }
99+
}
100+
assert new C(1).values == []
101+
'''
102+
}
103+
104+
@Test
105+
void testDefaultListInMiddleParam() {
106+
// exercises the bridge that drops *only* the trailing parameter,
107+
// leaving the list-default in non-final position when the next
108+
// bridge layer is generated.
109+
assertScript '''
110+
@groovy.transform.CompileStatic
111+
class C {
112+
List<String> values
113+
String tag
114+
C(int x, List<String> values = ['a'], String tag = 't') {
115+
this.values = values
116+
this.tag = tag
117+
}
118+
}
119+
assert new C(1).values == ['a']
120+
assert new C(1).tag == 't'
121+
assert new C(1, ['b']).tag == 't'
122+
'''
123+
}
124+
125+
@Test
126+
void testDefaultListClassParam_nonIndy() {
127+
// The bridge constructor's `new ArrayList(n)` is dispatched through a
128+
// dynamic call site even in non-indy mode, so the same CHECKCAST is
129+
// required there.
130+
CompilerConfiguration config = new CompilerConfiguration()
131+
config.optimizationOptions.put('indy', false)
132+
new GroovyShell(config).evaluate '''
133+
@groovy.transform.CompileStatic
134+
class C {
135+
List<Class> types
136+
C(Class<?> a, Object b, List<Class> targetTypes = [Object]) {
137+
this.types = targetTypes
138+
}
139+
}
140+
assert new C(String, "x").types == [Object]
141+
'''
142+
}
143+
}

0 commit comments

Comments
 (0)