This is an automated email from the ASF dual-hosted git repository.

garydgregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-bcel.git


The following commit(s) were added to refs/heads/master by this push:
     new e114e702 [BCEL-280] MethodGen.setMaxLocals() should account for 
LocalVariableTable entries (#529)
e114e702 is described below

commit e114e702e3eb44ae922583820a05efa81ce91d8a
Author: akashchamp <[email protected]>
AuthorDate: Sat Sep 26 04:15:58 2026 +0530

    [BCEL-280] MethodGen.setMaxLocals() should account for LocalVariableTable 
entries (#529)
    
    setMaxLocals() only scanned the InstructionList for
    LocalVariableInstruction/RET/IINC operands, ignoring any
    LocalVariableGen entries registered via addLocalVariable(). Some
    compilers (e.g. kotlinc) emit LocalVariableTable entries for local
    slots that no instruction in the method body ever touches, such as
    unused loop variables. When code later calls setMaxLocals() (for
    example after instrumentation), it silently shrinks maxLocals below
    what the local variable table requires, producing a class file that
    fails verification with "Invalid index ... in LocalVariableTable".
    
    setMaxLocals() now also considers each registered LocalVariableGen's
    index + type size, mirroring the bound addLocalVariable() already
    enforces when a variable is added directly.
    
    Added MethodGenTest.testSetMaxLocalsAccountsForLocalVariableTable(),
    which fails on unpatched code (maxLocals drops from 7 to 1) and
    passes with the fix.
    
    Generated-by: Claude (claude-sonnet-5)
    
    Co-authored-by: Claude Sonnet 5 <[email protected]>
---
 .../java/org/apache/bcel/generic/MethodGen.java    | 12 +++++++++-
 .../org/apache/bcel/generic/MethodGenTest.java     | 26 ++++++++++++++++++++++
 2 files changed, 37 insertions(+), 1 deletion(-)

diff --git a/src/main/java/org/apache/bcel/generic/MethodGen.java 
b/src/main/java/org/apache/bcel/generic/MethodGen.java
index f3bc120e..f928f42b 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 class MethodGen extends FieldGenOrMethodGen {
     }
 
     /**
-     * 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 class MethodGen extends FieldGenOrMethodGen {
                     }
                 }
             }
+            // 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 6cbd7210..b8cb5ee7 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 static org.junit.jupiter.api.Assertions.fail;
 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 @@ class MethodGenTest {
         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");
+    }
 }

Reply via email to