Description
Kotlin compiler generates additional fake local variables that are not used in method code, after processing such methods with BCEL fake variables are kept in local variable table but maxLocals value decreased to number of locals used in method bytecode.
As result "java.lang.ClassFormatError: Invalid index 6 in LocalVariableTable in class file test/Bug2" exception is thrown at runtime on attemt to load newly generated class.
Sample code:
bug.kt
package bug import org.apache.bcel.classfile.ClassParser import org.apache.bcel.generic.ClassGen import org.apache.bcel.generic.MethodGen class Bug { fun test() { val list = arrayListOf(1, 2, 3) list.forEach { println(it) } //here additional variable are generated - see bytecode below } } fun main(args: Array<String>) { val parser = ClassParser(ClassLoader.getSystemClassLoader().getResourceAsStream("bug/Bug.class"), "Bug.class"); val javaClass = parser.parse(); val classGen = ClassGen(javaClass) classGen.className = "test.Bug2" val originalMethod = classGen.methods.filter { it.name == "test" }.single() classGen.removeMethod(originalMethod) val newMethodGen = MethodGen(originalMethod, classGen.className, classGen.constantPool) newMethodGen.setMaxLocals() val newMethod = newMethodGen.method classGen.addMethod(newMethod) val bug2Class = classGen.javaClass // Exception in thread "main" java.lang.ClassFormatError: Invalid index 6 in LocalVariableTable in class file test/Bug2 // at java.lang.ClassLoader.defineClass1(Native Method) // at java.lang.ClassLoader.defineClass(ClassLoader.java:760) // at java.lang.ClassLoader.defineClass(ClassLoader.java:642) // at bug.BugKt$main$1.findClass(bug.kt:53) // at java.lang.ClassLoader.loadClass(ClassLoader.java:424) // at java.lang.ClassLoader.loadClass(ClassLoader.java:357) // at bug.BugKt.main(bug.kt:58) object : ClassLoader(ClassLoader.getSystemClassLoader()) { @Throws(ClassNotFoundException::class) protected override fun findClass(name: String): Class<*> { if (name == "test.Bug2") { val bytes = bug2Class.bytes return defineClass(name, bytes, 0, bytes.size) } return super.findClass(name) } }.loadClass("test.Bug2").newInstance() //8 != 6 assert(originalMethod.code.maxLocals == newMethod.code.maxLocals) }
Bug.test() original bytecode
// access flags 0x11 public final test()V L0 LINENUMBER 9 L0 ICONST_3 ANEWARRAY java/lang/Integer DUP ICONST_0 ICONST_1 INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer; AASTORE DUP ICONST_1 ICONST_2 INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer; AASTORE DUP ICONST_2 ICONST_3 INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer; AASTORE INVOKESTATIC kotlin/collections/CollectionsKt.arrayListOf ([Ljava/lang/Object;)Ljava/util/ArrayList; ASTORE 1 L1 LINENUMBER 10 L1 ALOAD 1 CHECKCAST java/lang/Iterable ASTORE 2 NOP L2 LINENUMBER 56 L2 ALOAD 2 INVOKEINTERFACE java/lang/Iterable.iterator ()Ljava/util/Iterator; ASTORE 3 L3 ALOAD 3 INVOKEINTERFACE java/util/Iterator.hasNext ()Z IFEQ L4 ALOAD 3 INVOKEINTERFACE java/util/Iterator.next ()Ljava/lang/Object; ASTORE 4 L5 ALOAD 4 CHECKCAST java/lang/Number INVOKEVIRTUAL java/lang/Number.intValue ()I ISTORE 5 L6 LINENUMBER 11 L6 NOP L7 GETSTATIC java/lang/System.out : Ljava/io/PrintStream; ILOAD 5 INVOKEVIRTUAL java/io/PrintStream.println (I)V L8 L9 LINENUMBER 11 L9 L10 LINENUMBER 12 L10 L11 NOP L12 GOTO L3 L4 LINENUMBER 57 L4 L13 L14 LINENUMBER 14 L14 RETURN L15 LOCALVARIABLE it I L6 L11 5 LOCALVARIABLE $i$a$1$forEach I L6 L11 6 LOCALVARIABLE element$iv Ljava/lang/Object; L5 L12 4 LOCALVARIABLE $receiver$iv Ljava/lang/Iterable; L2 L13 2 LOCALVARIABLE $i$f$forEach I L2 L13 7 LOCALVARIABLE list Ljava/util/ArrayList; L1 L15 1 LOCALVARIABLE this Lbug/Bug; L0 L15 0 MAXSTACK = 4 MAXLOCALS = 8