diff --git a/src/main/java/org/apache/bcel/generic/MethodGen.java b/src/main/java/org/apache/bcel/generic/MethodGen.java index f3bc120ef7..f928f42bbc 100644 --- a/src/main/java/org/apache/bcel/generic/MethodGen.java +++ b/src/main/java/org/apache/bcel/generic/MethodGen.java @@ -1192,7 +1192,8 @@ public void setInstructionList(final InstructionList il) { // TODO could be pack } /** - * Sets the maximum number of local variables by examining the method signature and instructions. + * Sets the maximum number of local variables by examining the method signature, the instructions and the local + * variable table entries added via {@link #addLocalVariable}. */ public void setMaxLocals() { // TODO could be package-protected? (some tests would need repackaging) if (il != null) { @@ -1209,6 +1210,15 @@ public void setMaxLocals() { // TODO could be package-protected? (some tests wou } } } + // Local variables may be registered (for example, copied from an existing LocalVariableTable) without + // being referenced by any instruction, e.g. unused variables emitted by some compilers. Account for + // those too, so a later call does not shrink maxLocals below what those entries require. + for (final LocalVariableGen lv : variableList) { + final int index = lv.getIndex() + lv.getType().getSize(); + if (index > max) { + max = index; + } + } maxLocals = max; } else { maxLocals = 0; diff --git a/src/test/java/org/apache/bcel/generic/MethodGenTest.java b/src/test/java/org/apache/bcel/generic/MethodGenTest.java index 6cbd721061..b8cb5ee74f 100644 --- a/src/test/java/org/apache/bcel/generic/MethodGenTest.java +++ b/src/test/java/org/apache/bcel/generic/MethodGenTest.java @@ -30,6 +30,7 @@ import java.util.Arrays; import java.util.List; +import org.apache.bcel.Const; import org.apache.bcel.Repository; import org.apache.bcel.classfile.JavaClass; import org.apache.bcel.classfile.Method; @@ -143,4 +144,29 @@ void testRemoveLocalVariables() throws Exception { assertNull(lv.getStart(), "scope start"); assertNull(lv.getEnd(), "scope end"); } + + @Test + void testSetMaxLocalsAccountsForLocalVariableTable() { + // BCEL-280: some compilers (e.g. kotlinc) emit LocalVariableTable entries for slots that no + // instruction in the method body ever touches. setMaxLocals() must not shrink maxLocals below + // what those entries require, or the resulting class file fails verification with a + // "Invalid index ... in LocalVariableTable" ClassFormatError. + final InstructionList il = new InstructionList(); + il.append(InstructionConst.ALOAD_0); + il.append(InstructionConst.RETURN); + + final MethodGen mg = new MethodGen(Const.ACC_PUBLIC, Type.VOID, Type.NO_ARGS, new String[0], "bar", "Foo", il, + new ConstantPoolGen()); + + final InstructionHandle start = il.getStart(); + final InstructionHandle end = il.getEnd(); + // Slot 6 is only referenced through the local variable table below, never by an instruction. + mg.addLocalVariable("unusedByCompiler", Type.INT, 6, start, end); + assertEquals(7, mg.getMaxLocals(), "addLocalVariable() should already have grown maxLocals"); + + // Simulate a later pass (e.g. instrumentation) recomputing maxLocals from the instruction list. + mg.setMaxLocals(); + + assertEquals(7, mg.getMaxLocals(), "setMaxLocals() dropped a local variable table entry unreferenced by instructions"); + } }