Skip to content

Commit 6152e24

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 83b68e5 commit 6152e24

File tree

3 files changed

+26
-17
lines changed

3 files changed

+26
-17
lines changed

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

Lines changed: 12 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
@@ -1142,15 +1142,16 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder {
11421142
}
11431143
}
11441144

1145-
if (style.isStatic) { bc.invokestatic (jowner, jname, mdescr) }
1146-
else if (style.isSpecial) { bc.invokespecial (jowner, jname, mdescr) }
1145+
val isInterface = receiver.isEmittedInterface
1146+
if (style.isStatic) { bc.invokestatic (jowner, jname, mdescr, itf = isInterface) }
1147+
else if (style.isSpecial) { bc.invokespecial (jowner, jname, mdescr, itf = isInterface) }
11471148
else if (style.isVirtual) {
11481149
if (needsInterfaceCall(receiver)) { bc.invokeinterface(jowner, jname, mdescr) }
11491150
else { bc.invokevirtual (jowner, jname, mdescr) }
11501151
}
11511152
else {
11521153
assert(style.isSuper, s"An unknown InvokeStyle: $style")
1153-
bc.invokespecial(jowner, jname, mdescr)
1154+
bc.invokespecial(jowner, jname, mdescr, itf = isInterface)
11541155
initModule()
11551156
}
11561157

@@ -1397,17 +1398,19 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder {
13971398
def genInvokeDynamicLambda(ctor: Symbol, lambdaTarget: Symbol, environmentSize: Int, functionalInterface: Symbol): BType = {
13981399
debuglog(s"Using invokedynamic rather than `new ${ctor.owner}`")
13991400
val generatedType = classBTypeFromSymbol(functionalInterface)
1401+
val isInterface = lambdaTarget.owner.isEmittedInterface
14001402
val invokeStyle =
14011403
if (lambdaTarget.isStaticMember) asm.Opcodes.H_INVOKESTATIC
14021404
else if (lambdaTarget.isPrivate || lambdaTarget.isClassConstructor) asm.Opcodes.H_INVOKESPECIAL
1403-
else if (lambdaTarget.owner.isInterface) asm.Opcodes.H_INVOKEINTERFACE
1405+
else if (isInterface) asm.Opcodes.H_INVOKEINTERFACE
14041406
else asm.Opcodes.H_INVOKEVIRTUAL
14051407

14061408
val targetHandle =
14071409
new asm.Handle(invokeStyle,
14081410
classBTypeFromSymbol(lambdaTarget.owner).internalName,
14091411
lambdaTarget.name.mangledString,
1410-
asmMethodType(lambdaTarget).descriptor)
1412+
asmMethodType(lambdaTarget).descriptor,
1413+
/* itf = */ isInterface)
14111414

14121415
val (a,b) = lambdaTarget.info.paramTypes.splitAt(environmentSize)
14131416
var (capturedParamsTypes, lambdaParamTypes) = if(int.doLabmdasFollowJVMMetafactoryOrder) (a,b) else (b,a)
@@ -1437,6 +1440,7 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder {
14371440
val lambdaMetaFactoryBootstrapHandle =
14381441
new asm.Handle(asm.Opcodes.H_INVOKESTATIC,
14391442
int.LambdaMetaFactory.javaBinaryName, int.MetafactoryName,
1440-
"(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;")
1443+
"(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;",
1444+
/* itf = */ int.LambdaMetaFactory.isEmittedInterface)
14411445

14421446
}

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
StringBuilderClassName,
217217
INSTANCE_CONSTRUCTOR_NAME,
218-
"()V"
218+
"()V",
219+
itf = false
219220
)
220221
}
221222

@@ -393,12 +394,12 @@ trait BCodeIdiomatic {
393394
final def rem(tk: BType): Unit = { emitPrimitive(JCodeMethodN.remOpcodes, tk) } // can-multi-thread
394395

395396
// can-multi-thread
396-
final def invokespecial(owner: String, name: String, desc: String): Unit = {
397-
jmethod.visitMethodInsn(Opcodes.INVOKESPECIAL, owner, name, desc, false)
397+
final def invokespecial(owner: String, name: String, desc: String, itf: Boolean): Unit = {
398+
jmethod.visitMethodInsn(Opcodes.INVOKESPECIAL, owner, name, desc, itf)
398399
}
399400
// can-multi-thread
400-
final def invokestatic(owner: String, name: String, desc: String): Unit = {
401-
jmethod.visitMethodInsn(Opcodes.INVOKESTATIC, owner, name, desc, false)
401+
final def invokestatic(owner: String, name: String, desc: String, itf: Boolean): Unit = {
402+
jmethod.visitMethodInsn(Opcodes.INVOKESTATIC, owner, name, desc, itf)
402403
}
403404
// can-multi-thread
404405
final def invokeinterface(owner: String, name: String, desc: String): Unit = {
@@ -409,10 +410,6 @@ trait BCodeIdiomatic {
409410
jmethod.visitMethodInsn(Opcodes.INVOKEVIRTUAL, owner, name, desc, false)
410411
}
411412

412-
final def invokedynamic(owner: String, name: String, desc: String): Unit = {
413-
jmethod.visitMethodInsn(Opcodes.INVOKEDYNAMIC, owner, name, desc)
414-
}
415-
416413
// can-multi-thread
417414
final def goTo(label: asm.Label): Unit = { jmethod.visitJumpInsn(Opcodes.GOTO, label) }
418415
// 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)