Skip to content

Commit a15a438

Browse files
committed
GROOVY-11982: Default methods in interface throw IncompatibleClassChangeError under indy=false
1 parent 917de79 commit a15a438

3 files changed

Lines changed: 105 additions & 2 deletions

File tree

src/main/java/org/codehaus/groovy/classgen/AsmClassGenerator.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1475,8 +1475,12 @@ public void visitVariableExpression(final VariableExpression expression) {
14751475
}
14761476

14771477
protected void createInterfaceSyntheticStaticFields() {
1478-
if (referencedClasses.isEmpty()) return;
14791478
var icl = controller.getInterfaceClassLoadingClass();
1479+
// GROOVY-11982: also materialise the helper when there are call sites
1480+
// (e.g. dynamic code in default methods under indy=false), otherwise
1481+
// CallSiteWriter routes INVOKESTATIC at a class that was never emitted
1482+
boolean hasCallSites = !controller.getCallSiteWriter().getCallSites().isEmpty();
1483+
if (referencedClasses.isEmpty() && !hasCallSites) return;
14801484
addInnerClass(icl);
14811485
for (Map.Entry<String, ClassNode> entry : referencedClasses.entrySet()) {
14821486
// generate a field node

src/main/java/org/codehaus/groovy/classgen/asm/CallSiteWriter.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,10 @@ public void makeSiteEntry() {
116116
if (controller.isNotClinit()) {
117117
MethodVisitor mv = controller.getMethodVisitor();
118118
mv.visitInsn(NOP); // GROOVY-9076: need this for debugger to support step into
119-
mv.visitMethodInsn(INVOKESTATIC, controller.getInternalClassName(), GET_CALLSITE_METHOD, GET_CALLSITE_DESC, false);
119+
// GROOVY-11982: for an interface, route to the helper inner class which
120+
// owns $getCallSiteArray (interfaces themselves never have it, and an
121+
// INVOKESTATIC Methodref against an interface owner throws ICCE)
122+
mv.visitMethodInsn(INVOKESTATIC, controller.getClassName(), GET_CALLSITE_METHOD, GET_CALLSITE_DESC, false);
120123
controller.getOperandStack().push(CALLSITE_ARRAY_TYPE);
121124
callSiteArrayVarIndex = controller.getCompileStack().defineTemporaryVariable("$local$callSiteArray", CALLSITE_ARRAY_TYPE, true);
122125
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
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+
/**
25+
* An interface default method whose body uses dynamic Groovy features (e.g. a
26+
* {@code GString} or a dynamic method call) needs the call-site array. Under
27+
* {@code indy=false} the bytecode prologue emitted at the top of the method
28+
* is {@code INVOKESTATIC $getCallSiteArray()}; the owner of that call must be
29+
* the synthetic helper class (not the interface itself), and the helper must
30+
* actually be materialised. Otherwise the JVM throws
31+
* {@code IncompatibleClassChangeError} at first invocation because a
32+
* {@code Methodref} cannot resolve to an interface owner.
33+
*/
34+
final class Groovy11982 {
35+
36+
@Test
37+
void testInterfaceDefaultMethodWithGStringNonIndy() {
38+
CompilerConfiguration config = new CompilerConfiguration()
39+
config.optimizationOptions.put('indy', false)
40+
new GroovyShell(config).evaluate '''
41+
interface IConfig {
42+
String getName()
43+
default String getDescription() {
44+
"config[name=${getName()}]"
45+
}
46+
}
47+
class ConfigImpl implements IConfig {
48+
String getName() { 'impl' }
49+
}
50+
assert new ConfigImpl().description == 'config[name=impl]'
51+
'''
52+
}
53+
54+
@Test
55+
void testInterfaceDefaultMethodCallingOtherDefaultNonIndy() {
56+
CompilerConfiguration config = new CompilerConfiguration()
57+
config.optimizationOptions.put('indy', false)
58+
new GroovyShell(config).evaluate '''
59+
interface IConfig {
60+
String getName()
61+
default String getDescription() {
62+
"config[name=${getName()}]"
63+
}
64+
default String greet() {
65+
"greeted as ${getDescription()}"
66+
}
67+
}
68+
class ConfigImpl implements IConfig {
69+
String getName() { 'impl' }
70+
}
71+
assert new ConfigImpl().greet() == 'greeted as config[name=impl]'
72+
'''
73+
}
74+
75+
@Test
76+
void testStaticConsumerCallsDefaultMethodNonIndy() {
77+
CompilerConfiguration config = new CompilerConfiguration()
78+
config.optimizationOptions.put('indy', false)
79+
new GroovyShell(config).evaluate '''
80+
interface IConfig {
81+
String getName()
82+
default String getDescription() {
83+
"config[name=${getName()}]"
84+
}
85+
}
86+
class ConfigImpl implements IConfig {
87+
String getName() { 'impl' }
88+
}
89+
@groovy.transform.CompileStatic
90+
class StaticConsumer {
91+
static String describe(IConfig c) { c.description }
92+
}
93+
assert StaticConsumer.describe(new ConfigImpl()) == 'config[name=impl]'
94+
'''
95+
}
96+
}

0 commit comments

Comments
 (0)