Skip to content

Commit 9bad490

Browse files
committed
Fix IncompatibleClassChangeError under Java 9
The itf flag of `visitMethodInsn` and `Handle` in ASM needs to be true when the method is defined in an interface. Java 8 ignores this but Java 9 fails at runtime with: java.lang.IncompatibleClassChangeError: Method ... must beInterfaceMethodref constant This commit is inspired by similar changes in scalac, in particular see 7d51b3f by Jason Zaugg (from scala#5251) and e619b03 by Lukas Rytz (from scala#5293). This issue was also discussed in scala#5452 (which does not matter for Dotty since we do not run the backend optimizer).
1 parent 52a9b6a commit 9bad490

File tree

3 files changed

+23
-17
lines changed

3 files changed

+23
-17
lines changed

src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -691,15 +691,15 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder {
691691
val nativeKind = tpeTK(expr)
692692
genLoad(expr, nativeKind)
693693
val MethodNameAndType(mname, methodType) = asmBoxTo(nativeKind)
694-
bc.invokestatic(BoxesRunTime.internalName, mname, methodType.descriptor)
694+
bc.invokestatic(BoxesRunTime.internalName, mname, methodType.descriptor, itf = false)
695695
generatedType = boxResultType(fun.symbol) // was toTypeKind(fun.symbol.tpe.resultType)
696696

697697
case Apply(fun, List(expr)) if isUnbox(fun.symbol) =>
698698
genLoad(expr)
699699
val boxType = unboxResultType(fun.symbol) // was toTypeKind(fun.symbol.owner.linkedClassOfClass.tpe)
700700
generatedType = boxType
701701
val MethodNameAndType(mname, methodType) = asmUnboxTo(boxType)
702-
bc.invokestatic(BoxesRunTime.internalName, mname, methodType.descriptor)
702+
bc.invokestatic(BoxesRunTime.internalName, mname, methodType.descriptor, itf = false)
703703

704704
case app @ Apply(fun, args) =>
705705
val sym = fun.symbol
@@ -1150,15 +1150,16 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder {
11501150
}
11511151
}
11521152

1153-
if (style.isStatic) { bc.invokestatic (jowner, jname, mdescr) }
1154-
else if (style.isSpecial) { bc.invokespecial (jowner, jname, mdescr) }
1153+
val isInterface = receiver.isEmittedInterface
1154+
if (style.isStatic) { bc.invokestatic (jowner, jname, mdescr, itf = isInterface) }
1155+
else if (style.isSpecial) { bc.invokespecial (jowner, jname, mdescr, itf = isInterface) }
11551156
else if (style.isVirtual) {
11561157
if (needsInterfaceCall(receiver)) { bc.invokeinterface(jowner, jname, mdescr) }
11571158
else { bc.invokevirtual (jowner, jname, mdescr) }
11581159
}
11591160
else {
11601161
assert(style.isSuper, s"An unknown InvokeStyle: $style")
1161-
bc.invokespecial(jowner, jname, mdescr)
1162+
bc.invokespecial(jowner, jname, mdescr, itf = isInterface)
11621163
initModule()
11631164
}
11641165

@@ -1405,7 +1406,7 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder {
14051406
def genInvokeDynamicLambda(ctor: Symbol, lambdaTarget: Symbol, environmentSize: Int, functionalInterface: Symbol): BType = {
14061407
debuglog(s"Using invokedynamic rather than `new ${ctor.owner}`")
14071408
val generatedType = classBTypeFromSymbol(functionalInterface)
1408-
val isInterface = lambdaTarget.owner.isInterface
1409+
val isInterface = lambdaTarget.owner.isEmittedInterface
14091410
val invokeStyle =
14101411
if (lambdaTarget.isStaticMember) asm.Opcodes.H_INVOKESTATIC
14111412
else if (lambdaTarget.isPrivate || lambdaTarget.isClassConstructor) asm.Opcodes.H_INVOKESPECIAL
@@ -1417,7 +1418,7 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder {
14171418
classBTypeFromSymbol(lambdaTarget.owner).internalName,
14181419
lambdaTarget.name.mangledString,
14191420
asmMethodType(lambdaTarget).descriptor,
1420-
isInterface)
1421+
/* itf = */ isInterface)
14211422

14221423
val (a,b) = lambdaTarget.info.paramTypes.splitAt(environmentSize)
14231424
var (capturedParamsTypes, lambdaParamTypes) = if(int.doLabmdasFollowJVMMetafactoryOrder) (a,b) else (b,a)
@@ -1448,6 +1449,6 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder {
14481449
new asm.Handle(asm.Opcodes.H_INVOKESTATIC,
14491450
int.LambdaMetaFactory.javaBinaryName, int.MetafactoryName,
14501451
"(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;",
1451-
int.LambdaMetaFactory.isInterface)
1452+
/* itf = */ int.LambdaMetaFactory.isEmittedInterface)
14521453

14531454
}

src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,8 @@ trait BCodeIdiomatic {
215215
invokespecial(
216216
JavaStringBuilderClassName,
217217
INSTANCE_CONSTRUCTOR_NAME,
218-
"()V"
218+
"()V",
219+
itf = false
219220
)
220221
}
221222

@@ -398,12 +399,12 @@ trait BCodeIdiomatic {
398399
final def rem(tk: BType): Unit = { emitPrimitive(JCodeMethodN.remOpcodes, tk) } // can-multi-thread
399400

400401
// can-multi-thread
401-
final def invokespecial(owner: String, name: String, desc: String): Unit = {
402-
jmethod.visitMethodInsn(Opcodes.INVOKESPECIAL, owner, name, desc, false)
402+
final def invokespecial(owner: String, name: String, desc: String, itf: Boolean): Unit = {
403+
jmethod.visitMethodInsn(Opcodes.INVOKESPECIAL, owner, name, desc, itf)
403404
}
404405
// can-multi-thread
405-
final def invokestatic(owner: String, name: String, desc: String): Unit = {
406-
jmethod.visitMethodInsn(Opcodes.INVOKESTATIC, owner, name, desc, false)
406+
final def invokestatic(owner: String, name: String, desc: String, itf: Boolean): Unit = {
407+
jmethod.visitMethodInsn(Opcodes.INVOKESTATIC, owner, name, desc, itf)
407408
}
408409
// can-multi-thread
409410
final def invokeinterface(owner: String, name: String, desc: String): Unit = {
@@ -414,10 +415,6 @@ trait BCodeIdiomatic {
414415
jmethod.visitMethodInsn(Opcodes.INVOKEVIRTUAL, owner, name, desc, false)
415416
}
416417

417-
final def invokedynamic(owner: String, name: String, desc: String): Unit = {
418-
jmethod.visitMethodInsn(Opcodes.INVOKEDYNAMIC, owner, name, desc, false)
419-
}
420-
421418
// can-multi-thread
422419
final def goTo(label: asm.Label): Unit = { jmethod.visitJumpInsn(Opcodes.GOTO, label) }
423420
// can-multi-thread

src/compiler/scala/tools/nsc/backend/jvm/BackendInterface.scala

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -440,6 +440,14 @@ abstract class BackendInterface extends BackendInterfaceDefinitions {
440440
def tpe: Type // todo whats the differentce between tpe and info?
441441
def thisType: Type
442442

443+
/** Does this symbol actually correspond to an interface that will be emitted?
444+
* In the backend, this should be preferred over `isInterface` because it
445+
* also returns true for the symbols of the fake companion objects we
446+
* create for Java-defined classes.
447+
*/
448+
final def isEmittedInterface: Boolean = isInterface ||
449+
isJavaDefined && isModuleClass && companionClass.isInterface
450+
443451
// tests
444452
def isClass: Boolean
445453
def isType: Boolean

0 commit comments

Comments
 (0)