[
https://issues.apache.org/jira/browse/BCEL-280?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
]
Max Kammerer updated BCEL-280:
------------------------------
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:
{code:title=bug.kt|borderStyle=solid}
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)
}
{code}
{code:title=Bug.test() original bytecode|borderStyle=solid}
// 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
{code}
was:
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:
{code:title=bug.kt|borderStyle=solid}
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)
}
{code}
{code:title=Bug.test() original bytecode borderStyle=solid}
// 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
{code}
> MethodGen.setMaxLocals() should take in account local variables defined in
> Local Variable table
> -----------------------------------------------------------------------------------------------
>
> Key: BCEL-280
> URL: https://issues.apache.org/jira/browse/BCEL-280
> Project: Commons BCEL
> Issue Type: Bug
> Components: Main
> Affects Versions: 6.0
> Environment: Ubuntu 14.04, Bcel 6.0
> Reporter: Max Kammerer
> Attachments: sample-project.zip
>
>
> 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:
> {code:title=bug.kt|borderStyle=solid}
> 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)
> }
> {code}
> {code:title=Bug.test() original bytecode|borderStyle=solid}
> // 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
> {code}
--
This message was sent by Atlassian JIRA
(v6.3.4#6332)